diff --git a/.classpath b/.classpath new file mode 100644 index 0000000000000000000000000000000000000000..de0545d5257256b6dbde07ba286dd3c145170f93 --- /dev/null +++ b/.classpath @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry including="**/*.java" kind="src" output="target/classes" path="src"> + <attributes> + <attribute name="optional" value="true"/> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.fx.ide.jdt.core.JAVAFX_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> + <attributes> + <attribute name="maven.pomderived" value="true"/> + </attributes> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="lib" path="lib/matheclipse-core-1.0.0-SNAPSHOT-jar-with-dependencies.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream-1.4.11.1.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/activation-1.1.1.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/cglib-nodep-2.2.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/dom4j-1.6.1.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/jdom-1.1.3.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/jdom2-2.0.5.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/jettison-1.2.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/joda-time-1.6.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/kxml2-min-2.3.0.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/stax-1.2.0.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/stax-api-1.0.1.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/wstx-asl-3.2.7.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/xmlpull-1.1.3.1.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/xom-1.1.jar"/> + <classpathentry kind="lib" path="lib/xstream/xstream/xpp3_min-1.1.4c.jar"/> + <classpathentry kind="output" path="target/classes"/> +</classpath> diff --git a/.project b/.project new file mode 100644 index 0000000000000000000000000000000000000000..fae6075216d798dc7255eb82a27b4eadc3e98e58 --- /dev/null +++ b/.project @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>Infographer</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.xtext.ui.shared.xtextBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.m2e.core.maven2Builder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.m2e.core.maven2Nature</nature> + <nature>org.eclipse.xtext.ui.shared.xtextNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000000000000000000000000000000000..91ca62e2761b9b632ad25bfa2bc6e543c97e4f0d --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.ltk.core.refactoring.prefs b/.settings/org.eclipse.ltk.core.refactoring.prefs new file mode 100644 index 0000000000000000000000000000000000000000..b196c64a3418b865f0476d2e21d11eae3dd4b2da --- /dev/null +++ b/.settings/org.eclipse.ltk.core.refactoring.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000000000000000000000000000000000000..f897a7f1cb2389f85fe6381425d29f0a9866fb65 --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/gallery/Outbreaks-impact.json b/gallery/Outbreaks-impact.json new file mode 100644 index 0000000000000000000000000000000000000000..1de3bc0f176039b8176715408d176943f16d208c --- /dev/null +++ b/gallery/Outbreaks-impact.json @@ -0,0 +1,317 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":65.0, + "y":1205.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":116.0625, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-33.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":80.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":136.0625, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":43.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":252.125, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":41.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":58.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":368.1875, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":64.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":115.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/Outbreaks-impact.wrk b/gallery/Outbreaks-impact.wrk new file mode 100644 index 0000000000000000000000000000000000000000..65f4ad2e1cd5bafcdfdfd172bd9aba21f7e09065 --- /dev/null +++ b/gallery/Outbreaks-impact.wrk @@ -0,0 +1,1954 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":122.0, + "y":908.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":28.0, + "height":85.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":28.0, + "height":129.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x808080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":73.0, + "y":0.0, + "width":28.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":102.0, + "y":0.0, + "width":28.0, + "height":68.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":494.0, + "y":913.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":136.0, + "y":972.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":20.0, + "y":133.0, + "width":25.0, + "height":162.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":106.0, + "y":80.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":193.0, + "y":206.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":281.0, + "y":135.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":277.0, + "y":34.0, + "width":25.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ], + "fill":"0xcce6ffff", + "opacity":0.3015873015873016, + "coloring":"Source", + "connections":[ + { + "origin":"2", + "destination":"5", + "weight":49.0 + }, + { + "origin":"3", + "destination":"4", + "weight":81.0 + }, + { + "origin":"1", + "destination":"2", + "weight":81.0 + }, + { + "origin":"1", + "destination":"3", + "weight":81.0 + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":65.0, + "y":1205.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":116.0625, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-33.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":80.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":136.0625, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":43.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":252.125, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":41.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":58.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":368.1875, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":1.0, + "width":29.0, + "height":-23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":29.0, + "y":1.0, + "width":29.0, + "height":64.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":58.0, + "y":1.0, + "width":29.0, + "height":115.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ], + "datasheet":[ + { + "type":"Table", + "row":7, + "column":0, + "nrows":12, + "ncolumns":6, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.id","id","A.x","x","height","fill"], + "variables":[ + { + "name":"A.id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Outbreak"], + "mappings":[ + { + "from":"1.1", + "to":"Sars" + }, + { + "from":"1.2", + "to":"Swine flue" + }, + { + "from":"1.3", + "to":"Ebola" + }, + { + "from":"1.4", + "to":"Zika" + } + ] + }, + { + "name":"A.x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Outbreak"], + "mappings":[ + { + "from":"20.0", + "to":"Sars" + }, + { + "from":"136.0625", + "to":"Swine flue" + }, + { + "from":"252.125", + "to":"Ebola" + }, + { + "from":"368.1875", + "to":"Zika" + } + ] + }, + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["Period"], + "mappings":[ + { + "from":"0x334db3ff", + "to":"From start to paek" + }, + { + "from":"0x8099ffff", + "to":"3 months after peak" + }, + { + "from":"0xb3ccffff", + "to":"1 month after peak" + } + ] + }, + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Percentage change"], + "function":"height/2" + }, + { + "name":"id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["id"], + "function":"id" + }, + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["x"], + "function":"x" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/UK-elections.json b/gallery/UK-elections.json new file mode 100644 index 0000000000000000000000000000000000000000..939c0e9064859d2b361fadcaea5900ee1674c0b1 --- /dev/null +++ b/gallery/UK-elections.json @@ -0,0 +1,270 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":55.0, + "y":968.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":30.0, + "y":8.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":4.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":15.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":48.0, + "width":15.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc33ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":15.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":214.0, + "width":15.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":475.0, + "y":8.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":4.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":15.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":15.0, + "height":17.62962962962963, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc33ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":67.62962962962963, + "width":15.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":217.62962962962962, + "width":15.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.20634920634920634, + "coloring":"Source", + "connections":[ + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":2.0 + }, + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":6.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":36.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":140.0 + }, + { + "origin":"1.1", + "destination":"2.2", + "weight":1.6296296296296295 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":2.0 + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/UK-elections.wrk b/gallery/UK-elections.wrk new file mode 100644 index 0000000000000000000000000000000000000000..8d04f493c7a29e8262287329809c862637e21c59 --- /dev/null +++ b/gallery/UK-elections.wrk @@ -0,0 +1,1936 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":122.0, + "y":908.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":28.0, + "height":85.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":28.0, + "height":129.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x808080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":73.0, + "y":0.0, + "width":28.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":102.0, + "y":0.0, + "width":28.0, + "height":68.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":494.0, + "y":913.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":136.0, + "y":972.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":20.0, + "y":133.0, + "width":25.0, + "height":162.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":106.0, + "y":80.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":193.0, + "y":206.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":281.0, + "y":135.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":277.0, + "y":34.0, + "width":25.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ], + "fill":"0xcce6ffff", + "opacity":0.3015873015873016, + "coloring":"Source", + "connections":[ + { + "origin":"3", + "destination":"4", + "weight":81.0 + }, + { + "origin":"1", + "destination":"2", + "weight":81.0 + }, + { + "origin":"1", + "destination":"3", + "weight":81.0 + }, + { + "origin":"2", + "destination":"5", + "weight":49.0 + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":69.0, + "y":871.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":30.0, + "y":8.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":16.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":15.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":60.0, + "width":15.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc33ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":15.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":250.0, + "width":15.0, + "height":95.95851848710946, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":475.0, + "y":8.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":16.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":15.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":58.0, + "width":15.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc33ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":15.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":254.0, + "width":15.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.20634920634920634, + "coloring":"Source", + "connections":[ + { + "origin":"1.3", + "destination":"2.3", + "weight":138.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":30.0 + }, + { + "origin":"1.1", + "destination":"2.2", + "weight":6.0 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":1.9585184871094583 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + } + ] + } + ], + "datasheet":[ + { + "type":"Table", + "row":8, + "column":0, + "nrows":12, + "ncolumns":3, + "source":"1", + "group":"1", + "wide":true, + "network":true, + "properties":["id","id","weight"], + "variables":[ + { + "name":"id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["source","destination"], + "mappings":[ + { + "from":"1.1.1", + "to":"No control" + }, + { + "from":"1.1.2", + "to":"Lib Dems" + }, + { + "from":"1.1.3", + "to":"Labour" + }, + { + "from":"1.1.4", + "to":"Conservative" + }, + { + "from":"1.2.1", + "to":"No control" + }, + { + "from":"1.2.2", + "to":"Lib Dems" + }, + { + "from":"1.2.3", + "to":"Labour" + }, + { + "from":"1.2.4", + "to":"Conservative" + } + ] + }, + { + "name":"weight", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Change"], + "function":"weight/2" + } + ] + }, + { + "type":"Table", + "row":24, + "column":0, + "nrows":8, + "ncolumns":6, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.id","id","A.x","y","height","fill"], + "variables":[ + { + "name":"A.id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["A.id"], + "mappings":[ + { + "from":"1.1", + "to":"Before 2018 Election" + }, + { + "from":"1.2", + "to":"After 2018 Election" + } + ] + }, + { + "name":"A.x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.x"], + "function":"A.x" + }, + { + "name":"fill", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["fill"], + "function":"fill" + }, + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["Control"], + "function":"height/2" + }, + { + "name":"id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["id"], + "function":"id" + }, + { + "name":"y", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["y"], + "function":"y" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/age-pyramid.json b/gallery/age-pyramid.json new file mode 100644 index 0000000000000000000000000000000000000000..0e53ab97cbe7e0a89d59b9eb80505c830d059dbe --- /dev/null +++ b/gallery/age-pyramid.json @@ -0,0 +1,409 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":223.0, + "y":1102.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-34.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":-57.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":-75.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":-105.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":-90.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":-64.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":-36.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":-29.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":-23.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":-18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":50.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":65.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":126.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":135.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":100.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":73.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":48.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":24.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/age-pyramid.wrk b/gallery/age-pyramid.wrk new file mode 100644 index 0000000000000000000000000000000000000000..a891b41184e9e2bb6031f12477e261c0504651c0 --- /dev/null +++ b/gallery/age-pyramid.wrk @@ -0,0 +1,1348 @@ +{ + "workspace": { + "library":[ + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":222.5, + "y":1071.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","stroke","thickness","rotation"], + "variable":["y","width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":88.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":16.0, + "width":16.0, + "height":91.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":98.0, + "y":903.5, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":23.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":51.0, + "width":16.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":68.0, + "width":16.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":223.0, + "width":16.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":406.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":49.0, + "width":16.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":74.0, + "width":16.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":227.0, + "width":16.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3253968253968254, + "coloring":"Source", + "connections":[ + { + "origin":"1.1", + "destination":"2.1", + "weight":30.0 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.1", + "destination":"2.2", + "weight":6.3953488372093 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":2.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":138.0 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":204.0, + "y":990.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":83.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":83.0, + "height":72.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":95.0, + "width":83.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":223.0, + "y":1102.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-34.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":-57.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":-75.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":-105.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":-90.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":-64.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":-36.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":-29.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":-23.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":-18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":50.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":65.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":126.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":135.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":100.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":73.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":48.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":24.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":223.0, + "y":1102.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-34.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":-57.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":-75.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":-105.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":-90.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":-64.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":-36.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":-29.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":-23.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":-18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":50.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":65.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":126.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":135.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":100.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":73.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":48.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":24.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ], + "datasheet":[] + } +} \ No newline at end of file diff --git a/gallery/barchart-outbreaks.json b/gallery/barchart-outbreaks.json new file mode 100644 index 0000000000000000000000000000000000000000..5f7e87114ff072169d7e4cad971943fda24cea04 --- /dev/null +++ b/gallery/barchart-outbreaks.json @@ -0,0 +1,317 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":125.5, + "y":1077.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":105.625, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":21.375, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":75.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":127.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-36.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":75.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":232.625, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":338.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":28.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":116.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/barchart-outbreaks.wrk b/gallery/barchart-outbreaks.wrk new file mode 100644 index 0000000000000000000000000000000000000000..107efd7cf4bca4be590295891fe911c751993777 --- /dev/null +++ b/gallery/barchart-outbreaks.wrk @@ -0,0 +1,1115 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":125.5, + "y":1077.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":105.625, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":21.375, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":75.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":127.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-36.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":75.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":232.625, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":338.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":27.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":27.0, + "height":-30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x334db3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":27.0, + "y":0.0, + "width":27.0, + "height":28.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcce6ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":0.0, + "width":27.0, + "height":116.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":125.0, + "y":1231.0, + "width":299.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Outbreak impact on market", + "fontsize":22.0, + "lock":"false" + } + ], + "datasheet":[ + { + "type":"Table", + "row":5, + "column":0, + "nrows":12, + "ncolumns":6, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.id","id","A.x","x","height","fill"], + "variables":[ + { + "name":"A.id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.id"], + "function":"A.id" + }, + { + "name":"A.x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Outbreak"], + "mappings":[ + { + "from":"21.375", + "to":"Sars" + }, + { + "from":"127.0", + "to":"Swine flu" + }, + { + "from":"232.625", + "to":"Ebola" + }, + { + "from":"338.0", + "to":"Zika" + } + ] + }, + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["Period"], + "mappings":[ + { + "from":"0x334db3ff", + "to":"From start to peak" + }, + { + "from":"0x8099ffff", + "to":"3 months after peak" + }, + { + "from":"0xcce6ffff", + "to":"1 month after peak" + } + ] + }, + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Change (%)"], + "function":"height/3" + }, + { + "name":"id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["id"], + "function":"id" + }, + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["x"], + "function":"x" + } + ] + }, + { + "type":"Value", + "row":20, + "column":2, + "nrows":1, + "ncolumns":2, + "source":"2", + "wide":true, + "network":false, + "properties":["text"], + "variables":[ + { + "name":"text", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Title"], + "function":"text" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/bookshelf.json b/gallery/bookshelf.json new file mode 100644 index 0000000000000000000000000000000000000000..b3f49efebd6799a00c21d4eff6307d260f88c97c --- /dev/null +++ b/gallery/bookshelf.json @@ -0,0 +1,7289 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":5, + "prefix":"E.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":64.5, + "y":853.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":40.0, + "curve":"None", + "common":["D.sticky-x","D.sticky-y","D.distribution-x","D.delta-x","D.distribution-y","D.delta-y","D.x","D.curve","D.stroke","D.thickness","D.rotation","C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["D.y","C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":13.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"1.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"1.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":26.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.2", + "level":2, + "prefix":"B.", + "x":48.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.3", + "level":2, + "prefix":"B.", + "x":96.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":91.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.4", + "level":2, + "prefix":"B.", + "x":150.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.5", + "level":2, + "prefix":"B.", + "x":204.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"1.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":283.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"1.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":82.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.5", + "level":2, + "prefix":"B.", + "x":158.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":59.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":144.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"2.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"2.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.2", + "level":2, + "prefix":"B.", + "x":48.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.3", + "level":2, + "prefix":"B.", + "x":96.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":27.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":13.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.4", + "level":2, + "prefix":"B.", + "x":150.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":39.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.5", + "level":2, + "prefix":"B.", + "x":190.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":20.0, + "height":60.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":20.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":255.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"2.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":85.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.5", + "level":2, + "prefix":"B.", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":32.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":32.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":269.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"3.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"3.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":56.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.2", + "level":2, + "prefix":"B.", + "x":39.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.3", + "level":2, + "prefix":"B.", + "x":71.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":60.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.4", + "level":2, + "prefix":"B.", + "x":118.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.5", + "level":2, + "prefix":"B.", + "x":181.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":48.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":11.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":269.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"3.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":52.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":76.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.5", + "level":2, + "prefix":"B.", + "x":155.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":48.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":389.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"4.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"4.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.2", + "level":2, + "prefix":"B.", + "x":41.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":58.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":18.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.3", + "level":2, + "prefix":"B.", + "x":82.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":61.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.4", + "level":2, + "prefix":"B.", + "x":136.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.5", + "level":2, + "prefix":"B.", + "x":189.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":46.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":46.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"4.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":267.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"4.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":63.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":52.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":45.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.5", + "level":2, + "prefix":"B.", + "x":158.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":66.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":13.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/bookshelf.wrk b/gallery/bookshelf.wrk new file mode 100644 index 0000000000000000000000000000000000000000..c64f5fde8c9274aa400c56fdba16fb3dba126959 --- /dev/null +++ b/gallery/bookshelf.wrk @@ -0,0 +1,10622 @@ +{ + "workspace": { + "library":[ + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1004.0, + "y":935.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":-72.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":-82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":-89.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":-93.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":-51.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":-26.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":30.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":86.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":103.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":125.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":66.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":36.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":9.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":255.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":85.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"5", + "level":2, + "prefix":"B.", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":32.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":32.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":13.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":26.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2", + "level":2, + "prefix":"B.", + "x":48.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.3", + "level":2, + "prefix":"B.", + "x":96.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":91.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.4", + "level":2, + "prefix":"B.", + "x":150.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.5", + "level":2, + "prefix":"B.", + "x":204.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":283.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":82.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.5", + "level":2, + "prefix":"B.", + "x":158.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":59.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":5, + "prefix":"E.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":64.5, + "y":853.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":40.0, + "curve":"None", + "common":["D.sticky-x","D.sticky-y","D.distribution-x","D.delta-x","D.distribution-y","D.delta-y","D.x","D.curve","D.stroke","D.thickness","D.rotation","C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["D.y","C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":13.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"1.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"1.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":26.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.2", + "level":2, + "prefix":"B.", + "x":48.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.3", + "level":2, + "prefix":"B.", + "x":96.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":91.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.4", + "level":2, + "prefix":"B.", + "x":150.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.5", + "level":2, + "prefix":"B.", + "x":204.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"1.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":283.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"1.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":82.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.5", + "level":2, + "prefix":"B.", + "x":158.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":59.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"1.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":144.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"2.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"2.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.2", + "level":2, + "prefix":"B.", + "x":48.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.3", + "level":2, + "prefix":"B.", + "x":96.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":27.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":13.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.4", + "level":2, + "prefix":"B.", + "x":150.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":39.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.5", + "level":2, + "prefix":"B.", + "x":190.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":20.0, + "height":60.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":20.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":255.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"2.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":85.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.5", + "level":2, + "prefix":"B.", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":32.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":32.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"2.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":269.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"3.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"3.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":56.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.2", + "level":2, + "prefix":"B.", + "x":39.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":49.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.3", + "level":2, + "prefix":"B.", + "x":71.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":60.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.4", + "level":2, + "prefix":"B.", + "x":118.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.5", + "level":2, + "prefix":"B.", + "x":181.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":48.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":11.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":269.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"3.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":52.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":76.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.5", + "level":2, + "prefix":"B.", + "x":155.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":48.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"3.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":7.0, + "y":389.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":15.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.curve","C.stroke","C.thickness","C.rotation","B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["C.x","B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","fill1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Collection", + "id":"4.1", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":20.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"4.1.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.2", + "level":2, + "prefix":"B.", + "x":41.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":58.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":18.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.3", + "level":2, + "prefix":"B.", + "x":82.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":61.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.4", + "level":2, + "prefix":"B.", + "x":136.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":57.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":60.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.1.5", + "level":2, + "prefix":"B.", + "x":189.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.1.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":46.0, + "height":80.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.1.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":46.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.1.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.1.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.1.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"4.2", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":267.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.y","B.rotation","A.sticky-x1","A.sticky-y1","A.distribution-x1","A.delta-x1","A.distribution-y1","A.delta-y1","A.x1","A.y1","A.rotation1","A.sticky-x2","reference-x1.1","reference-y1.1","x1.1","y1.1","shape1.1","fill1.1","stroke1.1","thickness1.1","rotation1.1","fill1.2","thickness1.2","reference-y2.1","y2.1","fill2.1","rotation2.1","rotation2.2","rotation2.3"], + "variable":["B.x","A.sticky-y2","A.distribution-x2","A.delta-x2","A.distribution-y2","A.delta-y2","A.x2","A.y2","A.rotation2","width1.1","height1.1","reference-x1.2","reference-y1.2","x1.2","y1.2","width1.2","height1.2","shape1.2","stroke1.2","rotation1.2","reference-x2.1","x2.1","width2.1","height2.1","shape2.1","stroke2.1","thickness2.1","reference-x2.2","reference-y2.2","x2.2","y2.2","width2.2","height2.2","shape2.2","fill2.2","stroke2.2","thickness2.2","reference-x2.3","reference-y2.3","x2.3","y2.3","width2.3","height2.3","shape2.3","fill2.3","stroke2.3","thickness2.3"], + "components":[ + { + "type":"Group", + "id":"4.2.1", + "level":2, + "prefix":"B.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":63.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":40.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.1.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.2", + "level":2, + "prefix":"B.", + "x":44.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":65.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":48.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.2.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.2.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.3", + "level":2, + "prefix":"B.", + "x":85.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.3.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.3.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":52.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.3.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":34.0, + "height":37.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.3.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.3.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.3.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.3.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.4", + "level":2, + "prefix":"B.", + "x":121.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.4.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.4.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":45.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.4.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":38.0, + "height":21.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.4.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.4.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.4.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.4.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":14.0, + "height":14.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "id":"4.2.5", + "level":2, + "prefix":"B.", + "x":158.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-31.0, + "delta-y":0.0, + "common":["A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x1","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.sticky-x"], + "public":["B.x","B.y","width1.1","width1.2","width2.1","width2.2","width2.3","height1.1","height1.2","fill1.1"], + "bindings":[ + ["A.sticky-y1","A.sticky-y2"], + ["A.distribution-x1","A.distribution-x2"], + ["A.delta-x1","A.delta-x2"], + ["A.distribution-y1","A.distribution-y2"], + ["A.delta-y1","A.delta-y2"], + ["A.x1","A.x2"], + ["A.y1","A.y2"], + ["A.rotation1","A.rotation2"], + ["reference-x1.1","reference-x1.2","reference-x2.1","reference-x2.2","reference-x2.3"], + ["reference-y1.1","reference-y1.2"], + ["reference-y2.1","reference-y2.2","reference-y2.3"], + ["x1.1","x1.2","x2.1","x2.2","x2.3"], + ["y1.1","y1.2"], + ["y2.1","y2.2","y2.3"], + ["width1.1","width1.2"], + ["width2.1","width2.2","width2.3"], + ["height1.1"], + ["height1.2"], + ["height2.1","height2.2","height2.3"], + ["shape1.1","shape1.2","shape2.1","shape2.2","shape2.3"], + ["fill1.1"], + ["fill1.2"], + ["fill2.1","fill2.2","fill2.3"], + ["stroke1.1","stroke1.2","stroke2.1","stroke2.2","stroke2.3"], + ["thickness1.1"], + ["thickness1.2","thickness2.1","thickness2.2","thickness2.3"], + ["rotation1.1","rotation1.2"], + ["rotation2.1"], + ["rotation2.2"], + ["rotation2.3"] + ], + "components":[ + { + "type":"Group", + "id":"4.2.5.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x1.1","y1.1"], + "variable":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.5.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":66.0, + "thickness":1.1349206349206349, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2.5.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":36.0, + "height":13.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d4d4d64", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4.2.5.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["x2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"4.2.5.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":45.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.5.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Rectangle", + "id":"4.2.5.2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":16.0, + "width":0.0, + "height":0.0, + "thickness":0.0, + "rotation":-12.0, + "fill":"0x333333ff", + "stroke":"0x4d4d4dff", + "shape":"Rectangle", + "lock":"true" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "datasheet":[ + { + "type":"Column", + "row":1, + "column":0, + "nrows":40, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["D.y"], + "variables":[ + { + "name":"D.y", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Year"], + "mappings":[ + { + "from":"13.0", + "to":"2017" + }, + { + "from":"144.0", + "to":"2018" + }, + { + "from":"269.0", + "to":"2019" + }, + { + "from":"389.0", + "to":"2020" + } + ] + } + ] + }, + { + "type":"Column", + "row":1, + "column":1, + "nrows":40, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["height1.1"], + "variables":[ + { + "name":"height1.1", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["Ratings"], + "function":"height1.1" + } + ] + }, + { + "type":"Column", + "row":1, + "column":2, + "nrows":40, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["width1.1"], + "variables":[ + { + "name":"width1.1", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Age"], + "function":"width1.1" + } + ] + }, + { + "type":"Column", + "row":1, + "column":3, + "nrows":40, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["width2.1"], + "variables":[ + { + "name":"width2.1", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Best Seller"], + "mappings":[ + { + "from":"0.0", + "to":"No" + }, + { + "from":"14.0", + "to":"Yes" + }, + { + "from":"16.0", + "to":"Yes" + } + ] + } + ] + }, + { + "type":"Column", + "row":1, + "column":4, + "nrows":40, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["fill1.1"], + "variables":[ + { + "name":"fill1.1", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["Gender"], + "mappings":[ + { + "from":"0xb3e6b3ff", + "to":"Fiction" + }, + { + "from":"0xe64d4dff", + "to":"Non-Fiction" + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/bubblechart.json b/gallery/bubblechart.json new file mode 100644 index 0000000000000000000000000000000000000000..0ead99d359ac2a221b1771d7b526f339f458410a --- /dev/null +++ b/gallery/bubblechart.json @@ -0,0 +1,375 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":191.0, + "y":1053.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","y","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":24.0, + "y":21.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":66.0, + "y":46.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":109.0, + "y":34.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":135.0, + "y":95.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":75.0, + "y":87.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":140.0, + "y":144.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":226.0, + "y":45.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":191.0, + "y":118.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":276.0, + "y":141.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":24.0, + "y":46.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":89.0, + "y":59.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":84.0, + "y":35.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":111.0, + "y":105.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":172.0, + "y":43.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":139.0, + "y":63.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":246.0, + "y":76.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":336.0, + "y":88.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":344.0, + "y":181.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/bubblechart.wrk b/gallery/bubblechart.wrk new file mode 100644 index 0000000000000000000000000000000000000000..06c4b3dbc3f10a29c47732ca90854338c3aecd76 --- /dev/null +++ b/gallery/bubblechart.wrk @@ -0,0 +1,1718 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":122.0, + "y":908.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":28.0, + "height":85.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":28.0, + "height":129.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x808080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":73.0, + "y":0.0, + "width":28.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":102.0, + "y":0.0, + "width":28.0, + "height":68.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":494.0, + "y":913.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":136.0, + "y":972.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":20.0, + "y":133.0, + "width":25.0, + "height":162.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":106.0, + "y":80.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":193.0, + "y":206.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":281.0, + "y":135.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":277.0, + "y":34.0, + "width":25.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ], + "fill":"0xcce6ffff", + "opacity":0.3015873015873016, + "coloring":"Source", + "connections":[ + { + "origin":"3", + "destination":"4", + "weight":81.0 + }, + { + "origin":"2", + "destination":"5", + "weight":49.0 + }, + { + "origin":"1", + "destination":"3", + "weight":81.0 + }, + { + "origin":"1", + "destination":"2", + "weight":81.0 + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":180.0, + "y":1059.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":6.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":95.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":101.0, + "width":73.0, + "height":20.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"your text", + "fontsize":14.0, + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":191.0, + "y":1053.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","y","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":24.0, + "y":21.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":66.0, + "y":46.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":109.0, + "y":34.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":135.0, + "y":95.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":75.0, + "y":87.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":140.0, + "y":144.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":226.0, + "y":45.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":191.0, + "y":118.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":276.0, + "y":141.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc99cc6f", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":24.0, + "y":46.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":89.0, + "y":59.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":84.0, + "y":35.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":111.0, + "y":105.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":172.0, + "y":43.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":139.0, + "y":63.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":246.0, + "y":76.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":336.0, + "y":88.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":344.0, + "y":181.0, + "width":48.0, + "height":48.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ff80", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + } + ], + "datasheet":[ + { + "type":"Table", + "row":4, + "column":0, + "nrows":18, + "ncolumns":7, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.id","id","x","y","width","height","fill"], + "variables":[ + { + "name":"A.id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.id"], + "function":"A.id" + }, + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["Sex"], + "mappings":[ + { + "from":"0x8099ff80", + "to":"Male" + }, + { + "from":"0xcc99cc6f", + "to":"Female" + } + ] + }, + { + "name":"height", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["Age"], + "mappings":[ + { + "from":"16.0", + "to":"14" + }, + { + "from":"30.0", + "to":"15" + }, + { + "from":"48.0", + "to":"16" + } + ] + }, + { + "name":"id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["id"], + "function":"id" + }, + { + "name":"width", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["width"], + "function":"width" + }, + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Weight (kg)"], + "function":"40+x/10" + }, + { + "name":"y", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Height (cm)"], + "function":"140+y/4" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/covid-deaths.json b/gallery/covid-deaths.json new file mode 100644 index 0000000000000000000000000000000000000000..b7d126b11f74c3015be3cbffc924ef63f28a6d33 --- /dev/null +++ b/gallery/covid-deaths.json @@ -0,0 +1,3737 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":68.5, + "y":908.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.thickness","A.rotation","reference-x","reference-y","x","width","height","shape","stroke","thickness","rotation"], + "variable":["A.stroke","y","fill"], + "components":[ + { + "type":"LineChart", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xe6e64dff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xb3e6b3ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xffb3b3ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0x4d66ccff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xcc3333ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xe6804dff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"7", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0x996699ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"7.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"8", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0x80801aff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"8.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/covid-deaths.wrk b/gallery/covid-deaths.wrk new file mode 100644 index 0000000000000000000000000000000000000000..af0d285173f6e5856a7a7239b8f9a00fa64d4ea4 --- /dev/null +++ b/gallery/covid-deaths.wrk @@ -0,0 +1,4015 @@ +{ + "workspace": { + "library":[], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":68.5, + "y":908.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.thickness","A.rotation","reference-x","reference-y","x","width","height","shape","stroke","thickness","rotation"], + "variable":["A.stroke","y","fill"], + "components":[ + { + "type":"LineChart", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xe6e64dff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xb3e6b3ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3e6b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xffb3b3ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xffb3b3ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0x4d66ccff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":80.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xcc3333ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0xe6804dff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe6804dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"7", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0x996699ff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"7.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":40.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":120.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":160.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x996699ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"8", + "level":1, + "prefix":"A.", + "thickness":4.166666666666666, + "stroke":"0x80801aff", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":32.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"8.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":27.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":59.0, + "y":200.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":91.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":123.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":155.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":187.0, + "y":240.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":219.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":251.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":283.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":315.0, + "y":280.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":347.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":379.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":411.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":443.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":475.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":507.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":539.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":571.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":603.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":635.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":667.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":699.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":731.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":763.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":795.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8.26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":827.0, + "y":320.0, + "width":12.0, + "height":12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x80801aff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":70.0, + "y":1267.0, + "width":479.0, + "height":29.0, + "rotation":0.0, + "fill":"0xe6e6e6ff", + "text":"Country Ranking according to COVID19 Deaths", + "fontsize":20.0, + "lock":"false" + } + ], + "datasheet":[ + { + "type":"Column", + "row":1, + "column":1, + "nrows":8, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":true, + "network":false, + "properties":["A.stroke"], + "variables":[ + { + "name":"A.stroke", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.stroke"], + "function":"A.stroke" + } + ] + }, + { + "type":"Column", + "row":1, + "column":2, + "nrows":8, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":true, + "network":false, + "properties":["fill"], + "variables":[ + { + "name":"fill", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["fill"], + "function":"fill" + } + ] + }, + { + "type":"Column", + "row":12, + "column":1, + "nrows":208, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.stroke"], + "variables":[ + { + "name":"A.stroke", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["Country"], + "mappings":[ + { + "from":"0x4d66ccff", + "to":"Iran" + }, + { + "from":"0x80801aff", + "to":"USA" + }, + { + "from":"0x996699ff", + "to":"UK" + }, + { + "from":"0xb3e6b3ff", + "to":"France" + }, + { + "from":"0xcc3333ff", + "to":"Italy" + }, + { + "from":"0xe6804dff", + "to":"Spain" + }, + { + "from":"0xe6e64dff", + "to":"Belgium" + }, + { + "from":"0xffb3b3ff", + "to":"Germany" + } + ] + } + ] + }, + { + "type":"Column", + "row":12, + "column":2, + "nrows":208, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Date"], + "mappings":[ + { + "from":"27.0", + "to":"26/3" + }, + { + "from":"59.0", + "to":"27/3" + }, + { + "from":"91.0", + "to":"28/3" + }, + { + "from":"123.0", + "to":"29/3" + }, + { + "from":"155.0", + "to":"30/3" + }, + { + "from":"187.0", + "to":"31/3" + }, + { + "from":"219.0", + "to":"1/4" + }, + { + "from":"251.0", + "to":"2/4" + }, + { + "from":"283.0", + "to":"3/4" + }, + { + "from":"315.0", + "to":"4/4" + }, + { + "from":"347.0", + "to":"5/4" + }, + { + "from":"379.0", + "to":"6/4" + }, + { + "from":"411.0", + "to":"7/4" + }, + { + "from":"443.0", + "to":"8/4" + }, + { + "from":"475.0", + "to":"9/4" + }, + { + "from":"507.0", + "to":"10/4" + }, + { + "from":"539.0", + "to":"11/4" + }, + { + "from":"571.0", + "to":"12/4" + }, + { + "from":"603.0", + "to":"13/4" + }, + { + "from":"635.0", + "to":"14/4" + }, + { + "from":"667.0", + "to":"15/4" + }, + { + "from":"699.0", + "to":"16/4" + }, + { + "from":"731.0", + "to":"17/4" + }, + { + "from":"763.0", + "to":"18/4" + }, + { + "from":"795.0", + "to":"19/4" + }, + { + "from":"827.0", + "to":"20/4" + } + ] + } + ] + }, + { + "type":"Column", + "row":12, + "column":3, + "nrows":208, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["y"], + "variables":[ + { + "name":"y", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Ranking"], + "function":"9-y/40" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/custom-areachart.json b/gallery/custom-areachart.json new file mode 100644 index 0000000000000000000000000000000000000000..25f639906c0dd7eb9e9b5d19dbec6f08de0bec0a --- /dev/null +++ b/gallery/custom-areachart.json @@ -0,0 +1,341 @@ +{ + "visualizations":[ + { + "type":"AreaChart", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":53.0, + "y":1039.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":135.0, + "delta-y":0.0, + "curve":"Area", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","reference-x1","reference-y1","x1","y1","width1","height1","shape1","fontsize","fill1","stroke1","thickness1","rotation1","y2","width2","shape2","fill2","reference-x3","width3","height3","fill3"], + "variable":["A.x","A.y","A.rotation","reference-x2","reference-y2","x2","height2","stroke2","thickness2","rotation2","reference-y3","x3","y3","text3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":48.0, + "y":28.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":88.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":116.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Paul", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":183.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":336.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":66.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":94.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Eloïse", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":318.0, + "y":109.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":15.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":88.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":116.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Daniel", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":453.0, + "y":162.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":33.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":50.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"4.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Eloïse", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/custom-areachart.wrk b/gallery/custom-areachart.wrk new file mode 100644 index 0000000000000000000000000000000000000000..897897624657a19b78a01d55c81438b4c3f792a3 --- /dev/null +++ b/gallery/custom-areachart.wrk @@ -0,0 +1,1869 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":497.0, + "y":1167.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":490.0, + "y":937.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":481.0, + "y":663.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":107.875, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["A.x","y","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":28.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":19.0, + "height":65.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":75.0, + "width":19.0, + "height":75.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":160.0, + "width":19.0, + "height":65.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":135.875, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["y","height"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":19.0, + "height":65.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":75.0, + "width":19.0, + "height":85.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":170.0, + "width":19.0, + "height":65.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":243.75, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["y","height"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":19.0, + "height":65.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":75.0, + "width":19.0, + "height":65.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":150.0, + "width":19.0, + "height":75.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcccc66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.5, + "coloring":"Destination", + "connections":[ + { + "origin":"2.2", + "destination":"3.1", + "weight":10.0 + }, + { + "origin":"1.3", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"2.3", + "destination":"3.3", + "weight":55.0 + }, + { + "origin":"2.3", + "destination":"3.2", + "weight":10.0 + }, + { + "origin":"1.2", + "destination":"2.3", + "weight":10.0 + }, + { + "origin":"2.1", + "destination":"3.1", + "weight":55.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":55.0 + }, + { + "origin":"2.2", + "destination":"3.3", + "weight":20.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":55.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":55.0 + }, + { + "origin":"1.2", + "destination":"2.1", + "weight":10.0 + }, + { + "origin":"1.1", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"2.2", + "destination":"3.2", + "weight":55.0 + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1004.0, + "y":935.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":-72.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":-82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":-89.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":-93.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":-51.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":-26.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":30.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":86.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":103.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":125.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":66.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":36.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":9.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"AreaChart", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":53.0, + "y":1039.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":135.0, + "delta-y":0.0, + "curve":"Area", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","reference-x1","reference-y1","x1","y1","width1","height1","shape1","fontsize","fill1","stroke1","thickness1","rotation1","y2","width2","shape2","fill2","reference-x3","width3","height3","fill3"], + "variable":["A.x","A.y","A.rotation","reference-x2","reference-y2","x2","height2","stroke2","thickness2","rotation2","reference-y3","x3","y3","text3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":48.0, + "y":28.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":88.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":116.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Paul", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":183.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":336.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":66.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":94.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Eloïse", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":318.0, + "y":109.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":15.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":88.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":116.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Daniel", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":453.0, + "y":162.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":33.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","stroke","thickness","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill"], + "public":["A.x","A.y","A.rotation","height2","text3"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":24.0, + "width":38.0, + "height":50.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + }, + { + "type":"Text", + "id":"4.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":64.0, + "height":27.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Eloïse", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + } + ], + "datasheet":[ + { + "type":"Table", + "row":5, + "column":0, + "nrows":4, + "ncolumns":5, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.id","A.x","A.y","A.rotation","height2"], + "variables":[ + { + "name":"A.id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.id"], + "function":"A.id" + }, + { + "name":"A.rotation", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.rotation"], + "function":"A.rotation" + }, + { + "name":"A.x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Day"], + "mappings":[ + { + "from":"48.0", + "to":"Monday" + }, + { + "from":"183.0", + "to":"Tuesday" + }, + { + "from":"318.0", + "to":"Wednesday" + }, + { + "from":"453.0", + "to":"Thursday" + } + ] + }, + { + "name":"A.y", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Points"], + "function":"A.y" + }, + { + "name":"height2", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["height2"], + "function":"height2" + } + ] + }, + { + "type":"Column", + "row":5, + "column":5, + "nrows":4, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["text3"], + "variables":[ + { + "name":"text3", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Leader"], + "function":"text3" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/custom-sankey.json b/gallery/custom-sankey.json new file mode 100644 index 0000000000000000000000000000000000000000..6f85ebf91e3e5865712d2501c41bfe7c82d6257f --- /dev/null +++ b/gallery/custom-sankey.json @@ -0,0 +1,378 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":132.0, + "y":981.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","shape1","fill1","stroke1","thickness1","rotation1","y2","height3","shape3","fill3"], + "variable":["A.x","A.y","reference-x2","reference-y2","x2","width2","height2","shape2","fill2","stroke2","thickness2","rotation2","reference-x3","reference-y3","x3","y3","width3","stroke3","thickness3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":-33.0, + "y":123.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":112.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":135.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":180.0, + "y":238.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":40.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99cc99ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":63.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":184.0, + "y":12.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":72.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":95.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":411.0, + "y":80.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":112.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":135.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3412698412698413, + "coloring":"Destination", + "connections":[ + { + "origin":"3.2", + "destination":"4.2", + "weight":72.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":40.0 + }, + { + "origin":"2.2", + "destination":"4.2", + "weight":40.0 + }, + { + "origin":"1.2", + "destination":"3.2", + "weight":72.0 + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/custom-sankey.wrk b/gallery/custom-sankey.wrk new file mode 100644 index 0000000000000000000000000000000000000000..4a9e21858b3a65bcff1b8102343116e12ef53e39 --- /dev/null +++ b/gallery/custom-sankey.wrk @@ -0,0 +1,1274 @@ +{ + "workspace": { + "library":[ + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1004.0, + "y":935.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":-72.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":-82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":-89.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":-93.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":-51.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":-26.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":30.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":86.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":103.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":125.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":66.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":36.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":9.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":222.5, + "y":1071.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","stroke","thickness","rotation"], + "variable":["y","width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":88.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":16.0, + "width":16.0, + "height":91.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":98.0, + "y":903.5, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":23.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":51.0, + "width":16.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":68.0, + "width":16.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":223.0, + "width":16.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":406.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":49.0, + "width":16.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":74.0, + "width":16.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":227.0, + "width":16.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3253968253968254, + "coloring":"Source", + "connections":[ + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":138.0 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.1", + "destination":"2.2", + "weight":6.3953488372093 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":30.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":2.0 + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":204.0, + "y":990.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":83.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":83.0, + "height":72.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":95.0, + "width":83.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":132.0, + "y":981.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","shape1","fill1","stroke1","thickness1","rotation1","y2","height3","shape3","fill3"], + "variable":["A.x","A.y","reference-x2","reference-y2","x2","width2","height2","shape2","fill2","stroke2","thickness2","rotation2","reference-x3","reference-y3","x3","y3","width3","stroke3","thickness3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":-33.0, + "y":123.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":112.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":135.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":180.0, + "y":238.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":40.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99cc99ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":63.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":184.0, + "y":12.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":72.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":95.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":411.0, + "y":80.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y","height2","fill2"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":79.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":79.0, + "height":112.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":135.0, + "width":79.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3412698412698413, + "coloring":"Destination", + "connections":[ + { + "origin":"3.2", + "destination":"4.2", + "weight":72.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":40.0 + }, + { + "origin":"2.2", + "destination":"4.2", + "weight":40.0 + }, + { + "origin":"1.2", + "destination":"3.2", + "weight":72.0 + } + ] + } + ], + "datasheet":[ + { + "type":"Column", + "row":6, + "column":1, + "nrows":4, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["height2"], + "variables":[ + { + "name":"height2", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["Budget"], + "function":"100*height2" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/greece-population-pyramid.json b/gallery/greece-population-pyramid.json new file mode 100644 index 0000000000000000000000000000000000000000..1b58e46fe792f78f5ee1f1baa34f1ac2c9fb32be --- /dev/null +++ b/gallery/greece-population-pyramid.json @@ -0,0 +1,1243 @@ +{ + "visualizations":[ + { + "type":"LineChart", + "level":3, + "prefix":"C.", + "thickness":5.119047619047619, + "stroke":"0x999999ff", + "x":89.5, + "y":869.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":341.5, + "delta-y":0.0, + "curve":"Bezier", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.curve","B.stroke","B.thickness","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","y","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.x","B.y","width"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":75.5, + "y":63.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-130.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":-134.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":-120.6003839616056, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":-98.3243975988336, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":-106.4741568986512, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":-90.4824457002472, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":-62.86374232842952, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":-43.45978541642792, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":-12.59860898930272, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":-0.99663771211612, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"1.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":119.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":120.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":110.9956950309328, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":97.7371717165488, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":113.2469871899664, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":91.1532320169608, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":73.52767898728864, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":53.31342786375032, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":18.98586978985128, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":2.04006387023608, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":417.0, + "y":208.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"2.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-81.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":-101.723079660188, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":-130.5284446207432, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":-127.5656618560784, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":-111.3384716071192, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":-88.0450444644344, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":-84.2346734539568, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":-55.85760488423456, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":-17.29088735525344, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":-2.603182268326088, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":75.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":93.0238933518584, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":119.0182296787376, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":120.5432303605032, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":109.5745642803936, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":89.6731977362528, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":95.510110933932, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":65.95495183868432, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":26.83355317264728, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":4.556773801851816, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":758.5, + "y":142.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"3.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-71.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":-85.8144901722616, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":-85.574294356376, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":-107.4604179266456, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":-124.1204059715872, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":-115.0304955641568, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":-92.073498981396, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":-68.14180153898904, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":-42.23333631753696, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":-8.26689570755536, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":64.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":78.39720532179687, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":76.87430166018808, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":97.8784036116768, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":117.6788644924, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":116.0830037514864, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":100.748902911688, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":78.43353312368224, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":58.04534568880568, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":11.3507046048504, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/greece-population-pyramid.wrk b/gallery/greece-population-pyramid.wrk new file mode 100644 index 0000000000000000000000000000000000000000..4ddb3f3d3ad0a5fdc073351c6aee1bbc3d44e091 --- /dev/null +++ b/gallery/greece-population-pyramid.wrk @@ -0,0 +1,2307 @@ +{ + "workspace": { + "library":[ + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":222.5, + "y":1071.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","stroke","thickness","rotation"], + "variable":["y","width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":88.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":16.0, + "width":16.0, + "height":91.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":98.0, + "y":903.5, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":23.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":51.0, + "width":16.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":68.0, + "width":16.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":223.0, + "width":16.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":406.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":49.0, + "width":16.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":74.0, + "width":16.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":227.0, + "width":16.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3253968253968254, + "coloring":"Source", + "connections":[ + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":2.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":30.0 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.1", + "destination":"2.2", + "weight":6.3953488372093 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":138.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":204.0, + "y":990.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["width1","width2","width3"], + ["shape1","shape2"], + ["shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":83.0, + "height":13.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x666666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":18.0, + "width":83.0, + "height":72.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Triangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":95.0, + "width":83.0, + "height":31.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":223.0, + "y":1102.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-34.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":-57.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":-75.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":-105.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":-90.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":-64.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":-36.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":-29.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":-23.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":-18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":123.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":50.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":65.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":46.0, + "width":126.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":69.0, + "width":135.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":92.0, + "width":100.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":115.0, + "width":73.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":138.0, + "width":48.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":161.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":184.0, + "width":24.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":207.0, + "width":18.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ], + "visualizations":[ + { + "type":"LineChart", + "level":3, + "prefix":"C.", + "thickness":5.119047619047619, + "stroke":"0x999999ff", + "x":89.5, + "y":869.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":341.5, + "delta-y":0.0, + "curve":"Bezier", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.curve","B.stroke","B.thickness","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","y","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.x","B.y","width"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":75.5, + "y":63.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-130.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":-134.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":-120.6003839616056, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":-98.3243975988336, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":-106.4741568986512, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":-90.4824457002472, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":-62.86374232842952, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":-43.45978541642792, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":-12.59860898930272, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":-0.99663771211612, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"1.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":119.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":120.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":110.9956950309328, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":97.7371717165488, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":113.2469871899664, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":91.1532320169608, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":73.52767898728864, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":53.31342786375032, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":18.98586978985128, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":2.04006387023608, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":417.0, + "y":208.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"2.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-81.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":-101.723079660188, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":-130.5284446207432, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":-127.5656618560784, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":-111.3384716071192, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":-88.0450444644344, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":-84.2346734539568, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":-55.85760488423456, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":-17.29088735525344, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":-2.603182268326088, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":75.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":93.0238933518584, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":119.0182296787376, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":120.5432303605032, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":109.5745642803936, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":89.6731977362528, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":95.510110933932, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":65.95495183868432, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":26.83355317264728, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":4.556773801851816, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":758.5, + "y":142.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"3.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":-71.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":-85.8144901722616, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":-85.574294356376, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":-107.4604179266456, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":-124.1204059715872, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":-115.0304955641568, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":-92.073498981396, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":-68.14180153898904, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":-42.23333631753696, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":-8.26689570755536, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x234ae3ac", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":3.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":64.0, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":26.0, + "width":78.39720532179687, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":76.87430166018808, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":78.0, + "width":97.8784036116768, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":104.0, + "width":117.6788644924, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":130.0, + "width":116.0830037514864, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":156.0, + "width":100.748902911688, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":182.0, + "width":78.43353312368224, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.9", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":208.0, + "width":58.04534568880568, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.10", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":234.0, + "width":11.3507046048504, + "height":23.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xde2323b1", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":94.0, + "y":1295.0, + "width":272.0, + "height":23.0, + "rotation":0.0, + "fill":"0x4d4d4dff", + "text":"Greek population evolution", + "fontsize":20.0, + "lock":"false" + } + ], + "datasheet":[ + { + "type":"Column", + "row":2, + "column":0, + "nrows":60, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["y"], + "variables":[ + { + "name":"y", + "type":"Functional", + "axis":true, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Age"], + "function":"0.39*y" + } + ] + }, + { + "type":"Column", + "row":2, + "column":1, + "nrows":60, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["width"], + "variables":[ + { + "name":"width", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["Percentage"], + "function":"width/8" + } + ] + }, + { + "type":"Column", + "row":8, + "column":2, + "nrows":3, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":true, + "network":false, + "properties":["B.x"], + "variables":[ + { + "name":"B.x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Year"], + "mappings":[ + { + "from":"75.5", + "to":"1980" + }, + { + "from":"417.0", + "to":"2000" + }, + { + "from":"758.5", + "to":"2020" + } + ] + } + ] + }, + { + "type":"Column", + "row":9, + "column":3, + "nrows":3, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":true, + "network":false, + "properties":["B.y"], + "variables":[ + { + "name":"B.y", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Population (x1000)"], + "function":"9000+10*B.y" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/people-infographics.json b/gallery/people-infographics.json new file mode 100644 index 0000000000000000000000000000000000000000..03240723e99642230427d78ddec9fda583b77220 --- /dev/null +++ b/gallery/people-infographics.json @@ -0,0 +1,1733 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":5, + "prefix":"E.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":308.0, + "y":304.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":297.875, + "delta-y":0.0, + "curve":"None", + "common":["D.sticky-x","D.sticky-y","D.distribution-x","D.delta-x","D.distribution-y","D.delta-y","D.y","D.curve","D.stroke","D.thickness","D.rotation","C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.rotation","B.sticky-x1","B.sticky-y1","B.distribution-x1","B.delta-x1","B.distribution-y1","B.delta-y1","B.x1","B.y1","B.rotation1","B.sticky-y2","B.distribution-y2","A.sticky-x1.1","A.sticky-y1.1","A.distribution-x1.1","A.delta-x1.1","A.distribution-y1.1","A.delta-y1.1","A.x1.1","A.y1.1","A.rotation1.1","A.sticky-x1.2","A.distribution-x1.2","A.delta-x1.2","A.y1.2","A.delta-x1.3","A.delta-x2.1","A.y2.1","reference-x1.1.1","reference-y1.1.1","x1.1.1","y1.1.1","width1.1.1","height1.1.1","shape1.1.1","fill1.1.1","stroke1.1.1","thickness1.1.1","rotation1.1.1","x1.1.2","rotation1.1.2","x1.2.1","width1.2.1","shape1.2.1","rotation1.2.1","x1.3.1","width1.3.1","shape1.3.1","rotation1.3.1","x1.3.2","rotation1.3.2","x2.1.1","width2.1.1","height2.1.1"], + "variable":["D.x","C.x","B.sticky-x2","B.distribution-x2","B.delta-x2","B.delta-y2","B.x2","B.y2","B.rotation2","A.sticky-y1.2","A.distribution-y1.2","A.delta-y1.2","A.x1.2","A.rotation1.2","A.sticky-x1.3","A.sticky-y1.3","A.distribution-x1.3","A.distribution-y1.3","A.delta-y1.3","A.x1.3","A.y1.3","A.rotation1.3","A.sticky-x2.1","A.sticky-y2.1","A.distribution-x2.1","A.distribution-y2.1","A.delta-y2.1","A.x2.1","A.rotation2.1","reference-x1.1.2","reference-y1.1.2","y1.1.2","width1.1.2","height1.1.2","shape1.1.2","fill1.1.2","stroke1.1.2","thickness1.1.2","reference-x1.2.1","reference-y1.2.1","y1.2.1","height1.2.1","fill1.2.1","stroke1.2.1","thickness1.2.1","reference-x1.3.1","reference-y1.3.1","y1.3.1","height1.3.1","fill1.3.1","stroke1.3.1","thickness1.3.1","reference-x1.3.2","reference-y1.3.2","y1.3.2","width1.3.2","height1.3.2","shape1.3.2","fill1.3.2","stroke1.3.2","thickness1.3.2","reference-x2.1.1","reference-y2.1.1","y2.1.1","shape2.1.1","fill2.1.1","stroke2.1.1","thickness2.1.1","rotation2.1.1"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":34.0, + "y":12.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":89.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.rotation","B.sticky-x1","B.sticky-y1","B.distribution-x1","B.delta-x1","B.distribution-y1","B.delta-y1","B.x1","B.y1","B.rotation1","B.sticky-y2","B.distribution-y2","A.sticky-x1.1","A.sticky-y1.1","A.distribution-x1.1","A.delta-x1.1","A.distribution-y1.1","A.delta-y1.1","A.x1.1","A.y1.1","A.rotation1.1","A.sticky-x1.2","A.distribution-x1.2","A.delta-x1.2","A.y1.2","A.delta-x2.1","A.y2.1","reference-x1.1.1","reference-y1.1.1","x1.1.1","y1.1.1","width1.1.1","height1.1.1","shape1.1.1","stroke1.1.1","thickness1.1.1","rotation1.1.1","x1.1.2","rotation1.1.2","x1.2.1","rotation1.2.1","width1.3.1","shape1.3.1","rotation1.3.1","rotation1.3.2","x2.1.1","width2.1.1","height2.1.1"], + "variable":["C.x","B.sticky-x2","B.distribution-x2","B.delta-x2","B.delta-y2","B.x2","B.y2","B.rotation2","A.sticky-y1.2","A.distribution-y1.2","A.delta-y1.2","A.x1.2","A.rotation1.2","A.sticky-x1.3","A.sticky-y1.3","A.distribution-x1.3","A.delta-x1.3","A.distribution-y1.3","A.delta-y1.3","A.x1.3","A.y1.3","A.rotation1.3","A.sticky-x2.1","A.sticky-y2.1","A.distribution-x2.1","A.distribution-y2.1","A.delta-y2.1","A.x2.1","A.rotation2.1","fill1.1.1","reference-x1.1.2","reference-y1.1.2","y1.1.2","width1.1.2","height1.1.2","shape1.1.2","fill1.1.2","stroke1.1.2","thickness1.1.2","reference-x1.2.1","reference-y1.2.1","y1.2.1","width1.2.1","height1.2.1","shape1.2.1","fill1.2.1","stroke1.2.1","thickness1.2.1","reference-x1.3.1","reference-y1.3.1","x1.3.1","y1.3.1","height1.3.1","fill1.3.1","stroke1.3.1","thickness1.3.1","reference-x1.3.2","reference-y1.3.2","x1.3.2","y1.3.2","width1.3.2","height1.3.2","shape1.3.2","fill1.3.2","stroke1.3.2","thickness1.3.2","reference-x2.1.1","reference-y2.1.1","y2.1.1","shape2.1.1","fill2.1.1","stroke2.1.1","thickness2.1.1","rotation2.1.1"], + "components":[ + { + "type":"Group", + "id":"1.1", + "level":3, + "prefix":"C.", + "x":-58.0, + "y":19.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-69.0, + "common":["B.sticky-x","B.distribution-x","B.delta-x","B.delta-y","B.x","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.sticky-y","B.distribution-y","B.y","A.delta-x","A.y","x"], + "public":["C.x","C.y","height1.1.1","height1.1.2","height1.3.1","height1.3.2","height1.2.1"], + "bindings":[ + ["B.sticky-x1","B.sticky-x2"], + ["B.distribution-x1","B.distribution-x2"], + ["B.delta-x1","B.delta-x2"], + ["B.delta-y1","B.delta-y2"], + ["B.x1","B.x2"], + ["B.rotation1","B.rotation2"], + ["A.sticky-x1.1","A.sticky-x1.3"], + ["A.sticky-x1.2","A.sticky-x2.1"], + ["A.sticky-y1.1","A.sticky-y1.2","A.sticky-y1.3","A.sticky-y2.1"], + ["A.distribution-x1.1","A.distribution-x1.3"], + ["A.distribution-x1.2","A.distribution-x2.1"], + ["A.distribution-y1.1","A.distribution-y1.2","A.distribution-y1.3","A.distribution-y2.1"], + ["A.delta-y1.1","A.delta-y1.2","A.delta-y1.3","A.delta-y2.1"], + ["A.x1.1","A.x1.2","A.x1.3","A.x2.1"], + ["A.rotation1.1","A.rotation1.2","A.rotation1.3","A.rotation2.1"], + ["reference-x1.1.1","reference-x1.1.2","reference-x1.2.1","reference-x1.3.1","reference-x1.3.2","reference-x2.1.1"], + ["reference-y1.1.1","reference-y1.1.2","reference-y1.2.1","reference-y1.3.1","reference-y1.3.2","reference-y2.1.1"], + ["y1.1.1","y1.1.2","y1.2.1","y1.3.1","y1.3.2","y2.1.1"], + ["width1.1.1","width1.1.2"], + ["width1.2.1"], + ["width1.3.1","width1.3.2"], + ["width2.1.1"], + ["height1.1.1","height1.1.2","height1.3.1","height1.3.2"], + ["height1.2.1"], + ["height2.1.1"], + ["shape1.1.1","shape1.1.2"], + ["shape1.2.1"], + ["shape1.3.1","shape1.3.2","shape2.1.1"], + ["fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + ["stroke1.1.1","stroke1.1.2","stroke1.2.1","stroke1.3.1","stroke1.3.2","stroke2.1.1"], + ["thickness1.1.1","thickness1.1.2","thickness1.2.1","thickness1.3.1","thickness1.3.2","thickness2.1.1"], + ["rotation1.1.1"], + ["rotation1.1.2"], + ["rotation1.2.1","rotation2.1.1"], + ["rotation1.3.1"], + ["rotation1.3.2"] + ], + "components":[ + { + "type":"Group", + "id":"1.1.1", + "level":2, + "prefix":"B.", + "x":58.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.x1.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"1.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.1.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-29.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":10.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":28.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":-10.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":68.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x1.2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Triangle", + "id":"1.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":125.0, + "height":134.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.1.1.3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":198.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["y1","y1.3.1","y3.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"1.1.1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-16.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":-160.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"1.1.1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":160.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.1.2", + "level":2, + "prefix":"B.", + "x":58.0, + "y":201.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.y1","A.x2.1","A.y2.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"1.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x2.1.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"1.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":46.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2", + "level":3, + "prefix":"C.", + "x":31.0, + "y":19.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-69.0, + "common":["B.sticky-x","B.distribution-x","B.delta-x","B.delta-y","B.x","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.sticky-y","B.distribution-y","B.y","A.delta-x","A.y","x"], + "public":["C.x","C.y","height1.1.1","height1.1.2","height1.3.1","height1.3.2","height1.2.1"], + "bindings":[ + ["B.sticky-x1","B.sticky-x2"], + ["B.distribution-x1","B.distribution-x2"], + ["B.delta-x1","B.delta-x2"], + ["B.delta-y1","B.delta-y2"], + ["B.x1","B.x2"], + ["B.rotation1","B.rotation2"], + ["A.sticky-x1.1","A.sticky-x1.3"], + ["A.sticky-x1.2","A.sticky-x2.1"], + ["A.sticky-y1.1","A.sticky-y1.2","A.sticky-y1.3","A.sticky-y2.1"], + ["A.distribution-x1.1","A.distribution-x1.3"], + ["A.distribution-x1.2","A.distribution-x2.1"], + ["A.distribution-y1.1","A.distribution-y1.2","A.distribution-y1.3","A.distribution-y2.1"], + ["A.delta-y1.1","A.delta-y1.2","A.delta-y1.3","A.delta-y2.1"], + ["A.x1.1","A.x1.2","A.x1.3","A.x2.1"], + ["A.rotation1.1","A.rotation1.2","A.rotation1.3","A.rotation2.1"], + ["reference-x1.1.1","reference-x1.1.2","reference-x1.2.1","reference-x1.3.1","reference-x1.3.2","reference-x2.1.1"], + ["reference-y1.1.1","reference-y1.1.2","reference-y1.2.1","reference-y1.3.1","reference-y1.3.2","reference-y2.1.1"], + ["y1.1.1","y1.1.2","y1.2.1","y1.3.1","y1.3.2","y2.1.1"], + ["width1.1.1","width1.1.2"], + ["width1.2.1"], + ["width1.3.1","width1.3.2"], + ["width2.1.1"], + ["height1.1.1","height1.1.2","height1.3.1","height1.3.2"], + ["height1.2.1"], + ["height2.1.1"], + ["shape1.1.1","shape1.1.2"], + ["shape1.2.1"], + ["shape1.3.1","shape1.3.2","shape2.1.1"], + ["fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + ["stroke1.1.1","stroke1.1.2","stroke1.2.1","stroke1.3.1","stroke1.3.2","stroke2.1.1"], + ["thickness1.1.1","thickness1.1.2","thickness1.2.1","thickness1.3.1","thickness1.3.2","thickness2.1.1"], + ["rotation1.1.1"], + ["rotation1.1.2"], + ["rotation1.2.1","rotation2.1.1"], + ["rotation1.3.1"], + ["rotation1.3.2"] + ], + "components":[ + { + "type":"Group", + "id":"1.2.1", + "level":2, + "prefix":"B.", + "x":58.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.x1.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"1.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.1.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-29.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":10.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":28.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":-10.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":68.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x1.2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"1.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":55.0, + "height":80.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"1.2.1.3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":144.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.3.1","y3.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"1.2.1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-26.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":-160.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"1.2.1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":26.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":160.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "id":"1.2.2", + "level":2, + "prefix":"B.", + "x":58.0, + "y":147.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.y1","A.x2.1","A.y2.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"1.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x2.1.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"1.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":46.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":331.875, + "y":12.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":89.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.rotation","B.sticky-x1","B.sticky-y1","B.distribution-x1","B.delta-x1","B.distribution-y1","B.delta-y1","B.x1","B.y1","B.rotation1","B.sticky-y2","B.distribution-y2","A.sticky-x1.1","A.sticky-y1.1","A.distribution-x1.1","A.delta-x1.1","A.distribution-y1.1","A.delta-y1.1","A.x1.1","A.y1.1","A.rotation1.1","A.sticky-x1.2","A.distribution-x1.2","A.delta-x1.2","A.y1.2","A.delta-x2.1","A.y2.1","reference-x1.1.1","reference-y1.1.1","x1.1.1","y1.1.1","width1.1.1","height1.1.1","shape1.1.1","stroke1.1.1","thickness1.1.1","rotation1.1.1","x1.1.2","rotation1.1.2","x1.2.1","rotation1.2.1","width1.3.1","shape1.3.1","rotation1.3.1","rotation1.3.2","x2.1.1","width2.1.1","height2.1.1"], + "variable":["C.x","B.sticky-x2","B.distribution-x2","B.delta-x2","B.delta-y2","B.x2","B.y2","B.rotation2","A.sticky-y1.2","A.distribution-y1.2","A.delta-y1.2","A.x1.2","A.rotation1.2","A.sticky-x1.3","A.sticky-y1.3","A.distribution-x1.3","A.delta-x1.3","A.distribution-y1.3","A.delta-y1.3","A.x1.3","A.y1.3","A.rotation1.3","A.sticky-x2.1","A.sticky-y2.1","A.distribution-x2.1","A.distribution-y2.1","A.delta-y2.1","A.x2.1","A.rotation2.1","fill1.1.1","reference-x1.1.2","reference-y1.1.2","y1.1.2","width1.1.2","height1.1.2","shape1.1.2","fill1.1.2","stroke1.1.2","thickness1.1.2","reference-x1.2.1","reference-y1.2.1","y1.2.1","width1.2.1","height1.2.1","shape1.2.1","fill1.2.1","stroke1.2.1","thickness1.2.1","reference-x1.3.1","reference-y1.3.1","x1.3.1","y1.3.1","height1.3.1","fill1.3.1","stroke1.3.1","thickness1.3.1","reference-x1.3.2","reference-y1.3.2","x1.3.2","y1.3.2","width1.3.2","height1.3.2","shape1.3.2","fill1.3.2","stroke1.3.2","thickness1.3.2","reference-x2.1.1","reference-y2.1.1","y2.1.1","shape2.1.1","fill2.1.1","stroke2.1.1","thickness2.1.1","rotation2.1.1"], + "components":[ + { + "type":"Group", + "id":"2.1", + "level":3, + "prefix":"C.", + "x":-58.0, + "y":19.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-69.0, + "common":["B.sticky-x","B.distribution-x","B.delta-x","B.delta-y","B.x","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.sticky-y","B.distribution-y","B.y","A.delta-x","A.y","x"], + "public":["C.x","C.y","height1.1.1","height1.1.2","height1.3.1","height1.3.2","height1.2.1","fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + "bindings":[ + ["B.sticky-x1","B.sticky-x2"], + ["B.distribution-x1","B.distribution-x2"], + ["B.delta-x1","B.delta-x2"], + ["B.delta-y1","B.delta-y2"], + ["B.x1","B.x2"], + ["B.rotation1","B.rotation2"], + ["A.sticky-x1.1","A.sticky-x1.3"], + ["A.sticky-x1.2","A.sticky-x2.1"], + ["A.sticky-y1.1","A.sticky-y1.2","A.sticky-y1.3","A.sticky-y2.1"], + ["A.distribution-x1.1","A.distribution-x1.3"], + ["A.distribution-x1.2","A.distribution-x2.1"], + ["A.distribution-y1.1","A.distribution-y1.2","A.distribution-y1.3","A.distribution-y2.1"], + ["A.delta-y1.1","A.delta-y1.2","A.delta-y1.3","A.delta-y2.1"], + ["A.x1.1","A.x1.2","A.x1.3","A.x2.1"], + ["A.rotation1.1","A.rotation1.2","A.rotation1.3","A.rotation2.1"], + ["reference-x1.1.1","reference-x1.1.2","reference-x1.2.1","reference-x1.3.1","reference-x1.3.2","reference-x2.1.1"], + ["reference-y1.1.1","reference-y1.1.2","reference-y1.2.1","reference-y1.3.1","reference-y1.3.2","reference-y2.1.1"], + ["y1.1.1","y1.1.2","y1.2.1","y1.3.1","y1.3.2","y2.1.1"], + ["width1.1.1","width1.1.2"], + ["width1.2.1"], + ["width1.3.1","width1.3.2"], + ["width2.1.1"], + ["height1.1.1","height1.1.2","height1.3.1","height1.3.2"], + ["height1.2.1"], + ["height2.1.1"], + ["shape1.1.1","shape1.1.2"], + ["shape1.2.1"], + ["shape1.3.1","shape1.3.2","shape2.1.1"], + ["fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + ["stroke1.1.1","stroke1.1.2","stroke1.2.1","stroke1.3.1","stroke1.3.2","stroke2.1.1"], + ["thickness1.1.1","thickness1.1.2","thickness1.2.1","thickness1.3.1","thickness1.3.2","thickness2.1.1"], + ["rotation1.1.1"], + ["rotation1.1.2"], + ["rotation1.2.1","rotation2.1.1"], + ["rotation1.3.1"], + ["rotation1.3.2"] + ], + "components":[ + { + "type":"Group", + "id":"2.1.1", + "level":2, + "prefix":"B.", + "x":58.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.x1.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"2.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.1.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-29.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":10.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":28.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":-10.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":68.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x1.2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Triangle", + "id":"2.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":125.0, + "height":77.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.1.1.3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":141.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["y1","y1.3.1","y3.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"2.1.1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-16.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":-160.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"2.1.1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":160.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.1.2", + "level":2, + "prefix":"B.", + "x":58.0, + "y":144.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.y1","A.x2.1","A.y2.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"2.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x2.1.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"2.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":46.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2", + "level":3, + "prefix":"C.", + "x":31.0, + "y":19.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-69.0, + "common":["B.sticky-x","B.distribution-x","B.delta-x","B.delta-y","B.x","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.sticky-y","B.distribution-y","B.y","A.delta-x","A.y","x"], + "public":["C.x","C.y","height1.1.1","height1.1.2","height1.3.1","height1.3.2","height1.2.1","fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + "bindings":[ + ["B.sticky-x1","B.sticky-x2"], + ["B.distribution-x1","B.distribution-x2"], + ["B.delta-x1","B.delta-x2"], + ["B.delta-y1","B.delta-y2"], + ["B.x1","B.x2"], + ["B.rotation1","B.rotation2"], + ["A.sticky-x1.1","A.sticky-x1.3"], + ["A.sticky-x1.2","A.sticky-x2.1"], + ["A.sticky-y1.1","A.sticky-y1.2","A.sticky-y1.3","A.sticky-y2.1"], + ["A.distribution-x1.1","A.distribution-x1.3"], + ["A.distribution-x1.2","A.distribution-x2.1"], + ["A.distribution-y1.1","A.distribution-y1.2","A.distribution-y1.3","A.distribution-y2.1"], + ["A.delta-y1.1","A.delta-y1.2","A.delta-y1.3","A.delta-y2.1"], + ["A.x1.1","A.x1.2","A.x1.3","A.x2.1"], + ["A.rotation1.1","A.rotation1.2","A.rotation1.3","A.rotation2.1"], + ["reference-x1.1.1","reference-x1.1.2","reference-x1.2.1","reference-x1.3.1","reference-x1.3.2","reference-x2.1.1"], + ["reference-y1.1.1","reference-y1.1.2","reference-y1.2.1","reference-y1.3.1","reference-y1.3.2","reference-y2.1.1"], + ["y1.1.1","y1.1.2","y1.2.1","y1.3.1","y1.3.2","y2.1.1"], + ["width1.1.1","width1.1.2"], + ["width1.2.1"], + ["width1.3.1","width1.3.2"], + ["width2.1.1"], + ["height1.1.1","height1.1.2","height1.3.1","height1.3.2"], + ["height1.2.1"], + ["height2.1.1"], + ["shape1.1.1","shape1.1.2","shape1.2.1"], + ["shape1.3.1","shape1.3.2","shape2.1.1"], + ["fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + ["stroke1.1.1","stroke1.1.2","stroke1.2.1","stroke1.3.1","stroke1.3.2","stroke2.1.1"], + ["thickness1.1.1","thickness1.1.2","thickness1.2.1","thickness1.3.1","thickness1.3.2","thickness2.1.1"], + ["rotation1.1.1"], + ["rotation1.1.2"], + ["rotation1.2.1","rotation2.1.1"], + ["rotation1.3.1"], + ["rotation1.3.2"] + ], + "components":[ + { + "type":"Group", + "id":"2.2.1", + "level":2, + "prefix":"B.", + "x":58.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.x1.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"2.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.1.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-29.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":10.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":28.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":-10.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":68.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x1.2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"2.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":55.0, + "height":132.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2.2.1.3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":196.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.3.1","y3.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"2.2.1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-26.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":-160.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"2.2.1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":26.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":160.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "id":"2.2.2", + "level":2, + "prefix":"B.", + "x":58.0, + "y":199.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.y1","A.x2.1","A.y2.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"2.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x2.1.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"2.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":46.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + } + ] + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":4, + "prefix":"D.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":629.75, + "y":12.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":89.0, + "delta-y":0.0, + "curve":"None", + "common":["C.sticky-x","C.sticky-y","C.distribution-x","C.delta-x","C.distribution-y","C.delta-y","C.y","C.rotation","B.sticky-x1","B.sticky-y1","B.distribution-x1","B.delta-x1","B.distribution-y1","B.delta-y1","B.x1","B.y1","B.rotation1","B.sticky-y2","B.distribution-y2","A.sticky-x1.1","A.sticky-y1.1","A.distribution-x1.1","A.delta-x1.1","A.distribution-y1.1","A.delta-y1.1","A.x1.1","A.y1.1","A.rotation1.1","A.sticky-x1.2","A.distribution-x1.2","A.delta-x1.2","A.y1.2","A.delta-x2.1","A.y2.1","reference-x1.1.1","reference-y1.1.1","x1.1.1","y1.1.1","width1.1.1","height1.1.1","shape1.1.1","stroke1.1.1","thickness1.1.1","rotation1.1.1","x1.1.2","rotation1.1.2","x1.2.1","rotation1.2.1","width1.3.1","shape1.3.1","rotation1.3.1","rotation1.3.2","x2.1.1","width2.1.1","height2.1.1"], + "variable":["C.x","B.sticky-x2","B.distribution-x2","B.delta-x2","B.delta-y2","B.x2","B.y2","B.rotation2","A.sticky-y1.2","A.distribution-y1.2","A.delta-y1.2","A.x1.2","A.rotation1.2","A.sticky-x1.3","A.sticky-y1.3","A.distribution-x1.3","A.delta-x1.3","A.distribution-y1.3","A.delta-y1.3","A.x1.3","A.y1.3","A.rotation1.3","A.sticky-x2.1","A.sticky-y2.1","A.distribution-x2.1","A.distribution-y2.1","A.delta-y2.1","A.x2.1","A.rotation2.1","fill1.1.1","reference-x1.1.2","reference-y1.1.2","y1.1.2","width1.1.2","height1.1.2","shape1.1.2","fill1.1.2","stroke1.1.2","thickness1.1.2","reference-x1.2.1","reference-y1.2.1","y1.2.1","width1.2.1","height1.2.1","shape1.2.1","fill1.2.1","stroke1.2.1","thickness1.2.1","reference-x1.3.1","reference-y1.3.1","x1.3.1","y1.3.1","height1.3.1","fill1.3.1","stroke1.3.1","thickness1.3.1","reference-x1.3.2","reference-y1.3.2","x1.3.2","y1.3.2","width1.3.2","height1.3.2","shape1.3.2","fill1.3.2","stroke1.3.2","thickness1.3.2","reference-x2.1.1","reference-y2.1.1","y2.1.1","shape2.1.1","fill2.1.1","stroke2.1.1","thickness2.1.1","rotation2.1.1"], + "components":[ + { + "type":"Group", + "id":"3.1", + "level":3, + "prefix":"C.", + "x":-59.0, + "y":19.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-69.0, + "common":["B.sticky-x","B.distribution-x","B.delta-x","B.delta-y","B.x","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.sticky-y","B.distribution-y","B.y","A.delta-x","A.y","x"], + "public":["C.x","C.y","height1.1.1","height1.1.2","height1.3.1","height1.3.2","height1.2.1"], + "bindings":[ + ["B.sticky-x1","B.sticky-x2"], + ["B.distribution-x1","B.distribution-x2"], + ["B.delta-x1","B.delta-x2"], + ["B.delta-y1","B.delta-y2"], + ["B.x1","B.x2"], + ["B.rotation1","B.rotation2"], + ["A.sticky-x1.1","A.sticky-x1.3"], + ["A.sticky-x1.2","A.sticky-x2.1"], + ["A.sticky-y1.1","A.sticky-y1.2","A.sticky-y1.3","A.sticky-y2.1"], + ["A.distribution-x1.1","A.distribution-x1.3"], + ["A.distribution-x1.2","A.distribution-x2.1"], + ["A.distribution-y1.1","A.distribution-y1.2","A.distribution-y1.3","A.distribution-y2.1"], + ["A.delta-y1.1","A.delta-y1.2","A.delta-y1.3","A.delta-y2.1"], + ["A.x1.1","A.x1.2","A.x1.3","A.x2.1"], + ["A.rotation1.1","A.rotation1.2","A.rotation1.3","A.rotation2.1"], + ["reference-x1.1.1","reference-x1.1.2","reference-x1.2.1","reference-x1.3.1","reference-x1.3.2","reference-x2.1.1"], + ["reference-y1.1.1","reference-y1.1.2","reference-y1.2.1","reference-y1.3.1","reference-y1.3.2","reference-y2.1.1"], + ["y1.1.1","y1.1.2","y1.2.1","y1.3.1","y1.3.2","y2.1.1"], + ["width1.1.1","width1.1.2"], + ["width1.2.1"], + ["width1.3.1","width1.3.2"], + ["width2.1.1"], + ["height1.1.1","height1.1.2","height1.3.1","height1.3.2"], + ["height1.2.1"], + ["height2.1.1"], + ["shape1.1.1","shape1.1.2"], + ["shape1.2.1"], + ["shape1.3.1","shape1.3.2","shape2.1.1"], + ["fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + ["stroke1.1.1","stroke1.1.2","stroke1.2.1","stroke1.3.1","stroke1.3.2","stroke2.1.1"], + ["thickness1.1.1","thickness1.1.2","thickness1.2.1","thickness1.3.1","thickness1.3.2","thickness2.1.1"], + ["rotation1.1.1"], + ["rotation1.1.2"], + ["rotation1.2.1","rotation2.1.1"], + ["rotation1.3.1"], + ["rotation1.3.2"] + ], + "components":[ + { + "type":"Group", + "id":"3.1.1", + "level":2, + "prefix":"B.", + "x":58.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.x1.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"3.1.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.1.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.1.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-29.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":10.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.1.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":28.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":-10.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":68.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x1.2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Triangle", + "id":"3.1.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":125.0, + "height":103.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Triangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.1.1.3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":167.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "common":["y1","y1.3.1","y3.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"3.1.1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-16.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":-160.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"3.1.1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":160.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.1.2", + "level":2, + "prefix":"B.", + "x":58.0, + "y":170.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.y1","A.x2.1","A.y2.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"3.1.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x2.1.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"3.1.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":46.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2", + "level":3, + "prefix":"C.", + "x":30.0, + "y":19.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-69.0, + "common":["B.sticky-x","B.distribution-x","B.delta-x","B.delta-y","B.x","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.sticky-y","B.distribution-y","B.y","A.delta-x","A.y","x"], + "public":["C.x","C.y","height1.1.1","height1.1.2","height1.3.1","height1.3.2","height1.2.1"], + "bindings":[ + ["B.sticky-x1","B.sticky-x2"], + ["B.distribution-x1","B.distribution-x2"], + ["B.delta-x1","B.delta-x2"], + ["B.delta-y1","B.delta-y2"], + ["B.x1","B.x2"], + ["B.rotation1","B.rotation2"], + ["A.sticky-x1.1","A.sticky-x1.3"], + ["A.sticky-x1.2","A.sticky-x2.1"], + ["A.sticky-y1.1","A.sticky-y1.2","A.sticky-y1.3","A.sticky-y2.1"], + ["A.distribution-x1.1","A.distribution-x1.3"], + ["A.distribution-x1.2","A.distribution-x2.1"], + ["A.distribution-y1.1","A.distribution-y1.2","A.distribution-y1.3","A.distribution-y2.1"], + ["A.delta-y1.1","A.delta-y1.2","A.delta-y1.3","A.delta-y2.1"], + ["A.x1.1","A.x1.2","A.x1.3","A.x2.1"], + ["A.rotation1.1","A.rotation1.2","A.rotation1.3","A.rotation2.1"], + ["reference-x1.1.1","reference-x1.1.2","reference-x1.2.1","reference-x1.3.1","reference-x1.3.2","reference-x2.1.1"], + ["reference-y1.1.1","reference-y1.1.2","reference-y1.2.1","reference-y1.3.1","reference-y1.3.2","reference-y2.1.1"], + ["y1.1.1","y1.1.2","y1.2.1","y1.3.1","y1.3.2","y2.1.1"], + ["width1.1.1","width1.1.2"], + ["width1.2.1"], + ["width1.3.1","width1.3.2"], + ["width2.1.1"], + ["height1.1.1","height1.1.2","height1.3.1","height1.3.2"], + ["height1.2.1"], + ["height2.1.1"], + ["shape1.1.1","shape1.1.2","shape1.2.1"], + ["shape1.3.1","shape1.3.2","shape2.1.1"], + ["fill1.1.1","fill1.1.2","fill1.2.1","fill1.3.1","fill1.3.2","fill2.1.1"], + ["stroke1.1.1","stroke1.1.2","stroke1.2.1","stroke1.3.1","stroke1.3.2","stroke2.1.1"], + ["thickness1.1.1","thickness1.1.2","thickness1.2.1","thickness1.3.1","thickness1.3.2","thickness2.1.1"], + ["rotation1.1.1"], + ["rotation1.1.2"], + ["rotation1.2.1","rotation2.1.1"], + ["rotation1.3.1"], + ["rotation1.3.2"] + ], + "components":[ + { + "type":"Group", + "id":"3.2.1", + "level":2, + "prefix":"B.", + "x":58.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.x1.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"3.2.1.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.1.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-29.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":10.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2.1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":28.0, + "y":0.0, + "width":16.0, + "height":72.0, + "thickness":1.5, + "rotation":-10.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.1.2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":68.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x1.2.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Rectangle", + "id":"3.2.1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":55.0, + "height":55.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3.2.1.3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":119.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":41.0, + "delta-y":0.0, + "common":["y1","y1.3.1","y3.1"], + "variable":["reference-x","reference-y","x","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"3.2.1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":-26.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":-160.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"3.2.1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":26.0, + "y":0.0, + "width":11.0, + "height":72.0, + "thickness":1.5, + "rotation":160.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "id":"3.2.2", + "level":2, + "prefix":"B.", + "x":58.0, + "y":122.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":-4.0, + "common":["A.x1","A.y1","A.x2.1","A.y2.1"], + "variable":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x","reference-y","x","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Group", + "id":"3.2.2.1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["x2.1.1"], + "variable":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "components":[ + { + "type":"Ellipse", + "id":"3.2.2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":44.0, + "height":46.0, + "thickness":1.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/people-infographics.wrk b/gallery/people-infographics.wrk new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/gallery/reanimations-deaths-France.json b/gallery/reanimations-deaths-France.json new file mode 100644 index 0000000000000000000000000000000000000000..4bd3ec115b52d4329708851a66887121b66b4dcf --- /dev/null +++ b/gallery/reanimations-deaths-France.json @@ -0,0 +1,998 @@ +{ + "visualizations":[ + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":2.325396825396825, + "stroke":"0x999999ff", + "x":109.0, + "y":624.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":26.0, + "delta-y":0.0, + "curve":"TopBottom", + "common":["reference-x","reference-y","width","shape","stroke","thickness","rotation"], + "variable":["x","y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":48.0, + "y":0.0, + "width":19.0, + "height":51.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":74.0, + "y":51.0, + "width":19.0, + "height":15.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":100.0, + "y":66.0, + "width":19.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":126.0, + "y":86.0, + "width":19.0, + "height":10.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":152.0, + "y":96.0, + "width":19.0, + "height":15.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":178.0, + "y":111.0, + "width":19.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":204.0, + "y":138.0, + "width":19.0, + "height":29.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":230.0, + "y":167.0, + "width":19.0, + "height":28.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":256.0, + "y":195.0, + "width":19.0, + "height":28.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":282.0, + "y":223.0, + "width":19.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":308.0, + "y":250.0, + "width":19.0, + "height":32.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":334.0, + "y":282.0, + "width":19.0, + "height":23.733333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":360.0, + "y":305.73333333333335, + "width":19.0, + "height":31.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":386.0, + "y":336.73333333333335, + "width":19.0, + "height":29.333333333333332, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":412.0, + "y":366.06666666666666, + "width":19.0, + "height":29.599999999999998, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":438.0, + "y":395.6666666666667, + "width":19.0, + "height":24.333333333333332, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":464.0, + "y":420.0, + "width":19.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":490.0, + "y":437.0, + "width":19.0, + "height":11.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":516.0, + "y":448.0, + "width":19.0, + "height":9.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":542.0, + "y":457.0, + "width":19.0, + "height":5.933333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":568.0, + "y":462.93333333333334, + "width":19.0, + "height":3.7333333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":594.0, + "y":466.6666666666667, + "width":19.0, + "height":1.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff8080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":620.0, + "y":467.6666666666667, + "width":19.0, + "height":-5.466666666666667, + "thickness":0.0, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":646.0, + "y":462.20000000000005, + "width":19.0, + "height":-4.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":672.0, + "y":458.20000000000005, + "width":19.0, + "height":-8.2, + "thickness":0.0, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":698.0, + "y":450.00000000000006, + "width":19.0, + "height":-2.533333333333333, + "thickness":0.0, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":724.0, + "y":447.4666666666667, + "width":19.0, + "height":-1.6, + "thickness":0.0, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":750.0, + "y":445.8666666666667, + "width":19.0, + "height":-6.066666666666666, + "thickness":0.0, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":4.4, + "stroke":"0x999999ff", + "x":910.0, + "y":624.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":26.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-754.0, + "y":4.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-728.0, + "y":7.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-702.0, + "y":9.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-676.0, + "y":10.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-650.0, + "y":13.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-624.0, + "y":17.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-598.0, + "y":22.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-572.0, + "y":28.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-546.0, + "y":33.92, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-520.0, + "y":40.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-494.0, + "y":46.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-468.0, + "y":52.120000000000005, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-442.0, + "y":60.480000000000004, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-416.0, + "y":70.46000000000001, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-390.0, + "y":80.64, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-364.0, + "y":90.06, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-338.0, + "y":101.82000000000001, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-312.0, + "y":110.64, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-286.0, + "y":117.78, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-260.0, + "y":129.88, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-234.0, + "y":142.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-208.0, + "y":152.64000000000001, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-182.0, + "y":160.88, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-156.0, + "y":171.96, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-130.0, + "y":178.86, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-104.0, + "y":185.06, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"27", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-78.0, + "y":191.76, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"28", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-52.0, + "y":202.58, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/reanimations-deaths-France.wrk b/gallery/reanimations-deaths-France.wrk new file mode 100644 index 0000000000000000000000000000000000000000..22dde6819229ec5a17e11ccda42564aa624af9dc --- /dev/null +++ b/gallery/reanimations-deaths-France.wrk @@ -0,0 +1,4226 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":91.0, + "y":91.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":23.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","fill","stroke","thickness","rotation"], + "variable":["y","width","height"], + "public":["A.x","A.y","A.rotation","height2"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["fill1","fill2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":70.0, + "height":15.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffcce6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":19.0, + "width":16.0, + "height":80.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffcce6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"LineChart", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0x99b3ffff", + "x":164.0, + "y":919.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.rotation","reference-x1","reference-y1","x1","width1","height1","shape1","fill1","stroke1","thickness1","rotation1","reference-y2","y2","width2","fill2","width3"], + "variable":["A.x","A.y","y1","reference-x2","x2","height2","shape2","stroke2","thickness2","rotation2","reference-x3","reference-y3","x3","y3","height3","shape3","fill3","stroke3","thickness3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":21.0, + "y":165.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":124.0, + "y":285.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-21.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":46.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":30.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":314.0, + "y":253.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-63.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":130.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":465.0, + "y":158.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":98.0, + "y":1042.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":8.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":281.0, + "height":146.0, + "thickness":0.8174603174603174, + "rotation":0.0, + "fill":"0xe6e6cc5e", + "stroke":"0x6680e6ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":154.0, + "width":213.0, + "height":25.0, + "rotation":0.0, + "fill":"0x4d4d4dff", + "text":"Title", + "fontsize":18.0, + "lock":"false" + } + ] + }, + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3b34dff", + "x":118.0, + "y":882.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":6.0, + "y":37.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":47.0, + "y":49.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":76.0, + "y":74.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":121.0, + "y":96.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":192.0, + "y":143.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":258.0, + "y":179.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":310.0, + "y":210.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":352.0, + "y":230.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":403.0, + "y":260.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":464.0, + "y":303.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":81.0, + "y":525.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":30.0, + "delta-y":0.0, + "curve":"TopBottom", + "common":["reference-x","reference-y","width","shape","stroke","thickness","rotation"], + "variable":["x","y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":20.0, + "height":51.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":52.0, + "y":51.0, + "width":20.0, + "height":15.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":82.0, + "y":66.0, + "width":20.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":112.0, + "y":86.0, + "width":20.0, + "height":10.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":142.0, + "y":96.0, + "width":20.0, + "height":15.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":172.0, + "y":111.0, + "width":20.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":202.0, + "y":138.0, + "width":20.0, + "height":29.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":232.0, + "y":167.0, + "width":20.0, + "height":28.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":262.0, + "y":195.0, + "width":20.0, + "height":28.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":292.0, + "y":223.0, + "width":20.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":322.0, + "y":250.0, + "width":20.0, + "height":32.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":352.0, + "y":282.0, + "width":20.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":382.0, + "y":306.0, + "width":20.0, + "height":31.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":412.0, + "y":337.0, + "width":20.0, + "height":29.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":442.0, + "y":366.0, + "width":20.0, + "height":30.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":472.0, + "y":396.0, + "width":20.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":502.0, + "y":420.0, + "width":20.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":532.0, + "y":437.0, + "width":20.0, + "height":11.133333333333333, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":562.0, + "y":448.1333333333333, + "width":20.0, + "height":9.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":592.0, + "y":457.1333333333333, + "width":20.0, + "height":5.933333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":622.0, + "y":463.06666666666666, + "width":20.0, + "height":3.7333333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":652.0, + "y":466.8, + "width":20.0, + "height":1.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":682.0, + "y":467.8, + "width":20.0, + "height":-5.466666666666667, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":712.0, + "y":462.33333333333337, + "width":20.0, + "height":-4.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":742.0, + "y":458.33333333333337, + "width":20.0, + "height":-8.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":772.0, + "y":450.33333333333337, + "width":20.0, + "height":-2.533333333333333, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":802.0, + "y":447.8, + "width":20.0, + "height":-1.6, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":832.0, + "y":446.2, + "width":20.0, + "height":-6.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":139.0, + "y":1099.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":8.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":534.0, + "height":258.0, + "thickness":0.8174603174603174, + "rotation":0.0, + "fill":"0xe6e6cc5e", + "stroke":"0x6680e6ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":266.0, + "width":213.0, + "height":25.0, + "rotation":0.0, + "fill":"0x4d4d4dff", + "text":"Paris hospitals only", + "fontsize":18.0, + "lock":"false" + } + ] + }, + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":81.0, + "y":525.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":30.0, + "delta-y":0.0, + "curve":"TopBottom", + "common":["reference-x","reference-y","width","shape","stroke","thickness","rotation"], + "variable":["x","y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":20.0, + "height":51.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":52.0, + "y":51.0, + "width":20.0, + "height":15.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":82.0, + "y":66.0, + "width":20.0, + "height":20.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":112.0, + "y":86.0, + "width":20.0, + "height":10.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":142.0, + "y":96.0, + "width":20.0, + "height":15.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":172.0, + "y":111.0, + "width":20.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":202.0, + "y":138.0, + "width":20.0, + "height":29.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":232.0, + "y":167.0, + "width":20.0, + "height":28.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":262.0, + "y":195.0, + "width":20.0, + "height":28.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":292.0, + "y":223.0, + "width":20.0, + "height":27.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":322.0, + "y":250.0, + "width":20.0, + "height":32.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":352.0, + "y":282.0, + "width":20.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":382.0, + "y":306.0, + "width":20.0, + "height":31.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":412.0, + "y":337.0, + "width":20.0, + "height":29.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":442.0, + "y":366.0, + "width":20.0, + "height":30.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":472.0, + "y":396.0, + "width":20.0, + "height":24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":502.0, + "y":420.0, + "width":20.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":532.0, + "y":437.0, + "width":20.0, + "height":11.133333333333333, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":562.0, + "y":448.1333333333333, + "width":20.0, + "height":9.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":592.0, + "y":457.1333333333333, + "width":20.0, + "height":5.933333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":622.0, + "y":463.06666666666666, + "width":20.0, + "height":3.7333333333333334, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":652.0, + "y":466.8, + "width":20.0, + "height":1.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":682.0, + "y":467.8, + "width":20.0, + "height":-5.466666666666667, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":712.0, + "y":462.33333333333337, + "width":20.0, + "height":-4.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":742.0, + "y":458.33333333333337, + "width":20.0, + "height":-8.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":772.0, + "y":450.33333333333337, + "width":20.0, + "height":-2.533333333333333, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":802.0, + "y":447.8, + "width":20.0, + "height":-1.6, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":832.0, + "y":446.2, + "width":20.0, + "height":-6.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":1.1, + "stroke":"0x808080ff", + "x":199.0, + "y":1118.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":16.0, + "delta-y":0.0, + "curve":"TopBottom", + "common":["reference-x","reference-y","width","shape","stroke","thickness","rotation"], + "variable":["x","y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":12.0, + "height":26.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":38.0, + "y":26.0, + "width":12.0, + "height":4.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":54.0, + "y":30.0, + "width":12.0, + "height":5.75, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":70.0, + "y":35.75, + "width":12.0, + "height":5.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":86.0, + "y":40.75, + "width":12.0, + "height":7.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":102.0, + "y":48.25, + "width":12.0, + "height":7.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":118.0, + "y":55.25, + "width":12.0, + "height":15.25, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":134.0, + "y":70.5, + "width":12.0, + "height":11.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":150.0, + "y":81.5, + "width":12.0, + "height":11.75, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":166.0, + "y":93.25, + "width":12.0, + "height":16.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":182.0, + "y":109.75, + "width":12.0, + "height":19.25, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":198.0, + "y":129.0, + "width":12.0, + "height":13.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":214.0, + "y":142.0, + "width":12.0, + "height":6.75, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":230.0, + "y":148.75, + "width":12.0, + "height":16.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":246.0, + "y":165.25, + "width":12.0, + "height":14.25, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":262.0, + "y":179.5, + "width":12.0, + "height":15.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":278.0, + "y":195.0, + "width":12.0, + "height":0.25, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":294.0, + "y":195.25, + "width":12.0, + "height":6.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":310.0, + "y":201.75, + "width":12.0, + "height":7.25, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":326.0, + "y":209.0, + "width":12.0, + "height":0.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":342.0, + "y":209.0, + "width":12.0, + "height":2.75, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":358.0, + "y":211.75, + "width":12.0, + "height":1.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":374.0, + "y":213.25, + "width":12.0, + "height":-6.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":390.0, + "y":207.25, + "width":12.0, + "height":2.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0xff6666ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":406.0, + "y":209.75, + "width":12.0, + "height":-7.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":422.0, + "y":202.25, + "width":12.0, + "height":-1.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":438.0, + "y":201.25, + "width":12.0, + "height":-2.75, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":454.0, + "y":198.5, + "width":12.0, + "height":-1.5, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"LineChart", + "level":1, + "prefix":"A.", + "thickness":2.9, + "stroke":"0xb3b34dff", + "x":963.0, + "y":525.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":30.0, + "delta-y":0.0, + "curve":"Bezier", + "common":["reference-x","reference-y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x","y"], + "components":[ + { + "type":"Ellipse", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-860.0, + "y":4.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-830.0, + "y":6.54, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-800.0, + "y":9.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-770.0, + "y":10.5, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-740.0, + "y":12.64, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-710.0, + "y":17.2, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-680.0, + "y":22.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-650.0, + "y":27.76, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-620.0, + "y":33.92, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"10", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-590.0, + "y":40.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"11", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-560.0, + "y":46.28, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"12", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-530.0, + "y":52.120000000000005, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"13", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-500.0, + "y":60.480000000000004, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"14", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-470.0, + "y":70.46000000000001, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"15", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-440.0, + "y":80.64, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"16", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-410.0, + "y":90.06, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"17", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-380.0, + "y":101.82000000000001, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"18", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-350.0, + "y":110.64, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"19", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-320.0, + "y":117.78, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"20", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-290.0, + "y":129.88, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"21", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-260.0, + "y":141.82, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"22", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-230.0, + "y":152.64000000000001, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"23", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-200.0, + "y":160.88, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"24", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-170.0, + "y":171.96, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"25", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-140.0, + "y":178.86, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"26", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-110.0, + "y":185.06, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"27", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-80.0, + "y":191.76, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"28", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":-50.0, + "y":202.58, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffffffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ], + "datasheet":[ + { + "type":"Column", + "row":1, + "column":0, + "nrows":28, + "ncolumns":1, + "source":"2", + "group":"2", + "wide":false, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Date"], + "mappings":[ + { + "from":"22.0", + "to":"18/3" + }, + { + "from":"52.0", + "to":"19/3" + }, + { + "from":"82.0", + "to":"20/3" + }, + { + "from":"112.0", + "to":"21/3" + }, + { + "from":"142.0", + "to":"22/3" + }, + { + "from":"172.0", + "to":"23/3" + }, + { + "from":"202.0", + "to":"24/3" + }, + { + "from":"232.0", + "to":"25/3" + }, + { + "from":"262.0", + "to":"26/3" + }, + { + "from":"292.0", + "to":"27/3" + }, + { + "from":"322.0", + "to":"28/3" + }, + { + "from":"352.0", + "to":"29/3" + }, + { + "from":"382.0", + "to":"30/3" + }, + { + "from":"412.0", + "to":"31/3" + }, + { + "from":"442.0", + "to":"1/4" + }, + { + "from":"472.0", + "to":"2/4" + }, + { + "from":"502.0", + "to":"3/4" + }, + { + "from":"532.0", + "to":"4/4" + }, + { + "from":"562.0", + "to":"5/4" + }, + { + "from":"592.0", + "to":"6/4" + }, + { + "from":"622.0", + "to":"7/4" + }, + { + "from":"652.0", + "to":"8/4" + }, + { + "from":"682.0", + "to":"9/4" + }, + { + "from":"712.0", + "to":"10/4" + }, + { + "from":"742.0", + "to":"11/4" + }, + { + "from":"772.0", + "to":"12/4" + }, + { + "from":"802.0", + "to":"13/4" + }, + { + "from":"832.0", + "to":"14/4" + } + ] + } + ] + }, + { + "type":"Column", + "row":1, + "column":1, + "nrows":28, + "ncolumns":1, + "source":"2", + "group":"2", + "wide":false, + "network":false, + "properties":["height"], + "variables":[ + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":true, + "labels":["Reanimations"], + "function":"15*height" + } + ] + }, + { + "type":"Column", + "row":1, + "column":2, + "nrows":28, + "ncolumns":1, + "source":"2", + "group":"2", + "wide":false, + "network":false, + "properties":["fill"], + "variables":[ + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Trend"], + "mappings":[ + { + "from":"0x8099ffff", + "to":"Decr" + }, + { + "from":"0xff6666ff", + "to":"incr" + } + ] + } + ] + }, + { + "type":"Table", + "row":33, + "column":0, + "nrows":28, + "ncolumns":5, + "source":"3", + "group":"3", + "wide":false, + "network":false, + "properties":["id","x","y","height","fill"], + "variables":[ + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Trend"], + "mappings":[ + { + "from":"0x8099ffff", + "to":"A" + }, + { + "from":"0xff6666ff", + "to":"B" + } + ] + }, + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":true, + "labels":["Reanimations"], + "function":"4*height" + }, + { + "name":"id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["id"], + "function":"id" + }, + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["x"], + "function":"x" + }, + { + "name":"y", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["y"], + "function":"y" + } + ] + }, + { + "type":"Column", + "row":65, + "column":3, + "nrows":28, + "ncolumns":1, + "source":"4", + "group":"4", + "wide":true, + "network":false, + "properties":["y"], + "variables":[ + { + "name":"y", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Deaths"], + "function":"50*y" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/red-blue-america-legend.wrk b/gallery/red-blue-america-legend.wrk new file mode 100644 index 0000000000000000000000000000000000000000..16f14c0aaadabc7635596bf6a2a458da6d727b39 --- /dev/null +++ b/gallery/red-blue-america-legend.wrk @@ -0,0 +1,11257 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":494.0, + "y":913.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":136.0, + "y":972.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":20.0, + "y":133.0, + "width":25.0, + "height":162.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":106.0, + "y":80.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":193.0, + "y":206.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":281.0, + "y":135.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":277.0, + "y":34.0, + "width":25.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ], + "fill":"0xcce6ffff", + "opacity":0.3015873015873016, + "coloring":"Source", + "connections":[ + { + "origin":"2", + "destination":"5", + "weight":49.0 + }, + { + "origin":"1", + "destination":"3", + "weight":81.0 + }, + { + "origin":"1", + "destination":"2", + "weight":81.0 + }, + { + "origin":"3", + "destination":"4", + "weight":81.0 + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":187.0, + "y":1123.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":23.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":22.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":23.0, + "y":0.0, + "width":22.0, + "height":98.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":46.0, + "y":0.0, + "width":22.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":69.0, + "y":0.0, + "width":22.0, + "height":131.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":92.0, + "y":0.0, + "width":22.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":332.0, + "y":1116.3333333333333, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","y","shape","stroke","thickness","rotation"], + "variable":["width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["y1","y2","y3"], + ["shape1","shape2","shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Ellipse", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":180.0, + "height":180.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99cc99ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":124.0, + "height":124.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":62.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":27.0, + "y":530.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":14.0, + "common":["reference-x","reference-y","x","stroke","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["stroke1"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":690.0, + "height":286.0, + "thickness":4.4, + "rotation":0.0, + "fill":"0xf3ece5ff", + "stroke":"0x666666ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":300.0, + "width":179.0, + "height":20.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Add your Title", + "fontsize":20.0, + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Rectangle", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":266.0, + "y":1191.0, + "width":353.0, + "height":135.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xeaf0f4ff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":72.0, + "y":563.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["A.x","A.y","x","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":41.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-4.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-7.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-18.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-29.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":5.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":2.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-2.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.0999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":700.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":18.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":18.9, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":25.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":22.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":15.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"7", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"7.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":28.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":28.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":22.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":22.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":25.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":29.099999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":33.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":30.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":33.599999999999994, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"8", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"8.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":17.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":11.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.449999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-3.1500000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"9", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"9.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":22.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":23.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":21.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":27.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":29.400000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":26.099999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":26.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"10", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"10.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-2.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"11", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"11.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":10.350000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"12", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"12.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":18.6, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":14.549999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":25.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":30.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":29.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":32.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"13", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"13.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":17.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":10.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"14", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"14.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":4.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":16.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":14.700000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"15", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"15.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":19.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":25.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":28.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"16", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"16.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":16.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":17.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":17.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"17", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"17.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":13.799999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":16.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":22.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":23.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":23.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"18", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"18.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":16.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":14.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"19", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"19.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.8999999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":15.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":17.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":20.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":15.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":15.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"20", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"20.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":17.85, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"21", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"21.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":4.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"22", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"22.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"23", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"23.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"24", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"24.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.4499999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"25", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"25.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.4499999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":11.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"26", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"26.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":12.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":17.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"27", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"27.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":15.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":19.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"28", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"28.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.8999999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":13.049999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"29", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"29.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-8.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-11.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"30", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"30.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"31", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"31.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":14.700000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":19.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":20.85, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"32", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"32.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":6.449999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":4.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"33", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"33.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"34", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"34.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"35", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"35.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-4.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"36", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"36.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-21.6, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"37", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"37.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"38", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"38.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"39", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"39.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"40", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":969.0, + "y":7.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"40.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"41", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"41.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-54.150000000000006, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-64.05000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-63.300000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-54.150000000000006, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-56.699999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-61.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-61.050000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-60.300000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"42", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"42.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-12.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-15.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"43", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"43.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.1500000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.45, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"44", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"44.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-13.049999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-18.15, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-16.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"45", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"45.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"46", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"46.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-10.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"47", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"47.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-12.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-21.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-21.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-14.549999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"48", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"48.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-23.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"49", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"49.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-14.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-12.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-22.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-20.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-16.799999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"50", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"50.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"51", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1195.0, + "y":708.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"51.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-8.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":322.0, + "y":1269.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":19.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":31.0, + "y":0.0, + "width":19.0, + "height":-31.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":51.0, + "y":0.0, + "width":19.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.0, + "y":0.0, + "width":19.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":91.0, + "y":0.0, + "width":19.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":111.0, + "y":0.0, + "width":19.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":131.0, + "y":0.0, + "width":19.0, + "height":6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":151.0, + "y":0.0, + "width":19.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.0, + "y":0.0, + "width":19.0, + "height":35.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "datasheet":[ + { + "type":"Column", + "row":1, + "column":0, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["A.id"], + "variables":[ + { + "name":"A.id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["State"], + "mappings":[ + { + "from":"2.1", + "to":"Hawaii" + }, + { + "from":"2.10", + "to":"New Mexico" + }, + { + "from":"2.11", + "to":"Colorado" + }, + { + "from":"2.12", + "to":"Wyoming" + }, + { + "from":"2.13", + "to":"Montana" + }, + { + "from":"2.14", + "to":"Texas" + }, + { + "from":"2.15", + "to":"Oklahoma" + }, + { + "from":"2.16", + "to":"Kansas" + }, + { + "from":"2.17", + "to":"Nebraska" + }, + { + "from":"2.18", + "to":"South Dakota" + }, + { + "from":"2.19", + "to":"North Dakota" + }, + { + "from":"2.2", + "to":"California" + }, + { + "from":"2.20", + "to":"Louisiana" + }, + { + "from":"2.21", + "to":"Arkansas" + }, + { + "from":"2.22", + "to":"Missouri" + }, + { + "from":"2.23", + "to":"Iowa" + }, + { + "from":"2.24", + "to":"Minnesota" + }, + { + "from":"2.25", + "to":"Mississippi" + }, + { + "from":"2.26", + "to":"Tennessee" + }, + { + "from":"2.27", + "to":"Kentucky" + }, + { + "from":"2.28", + "to":"Indiana" + }, + { + "from":"2.29", + "to":"Illinois" + }, + { + "from":"2.3", + "to":"Oregon" + }, + { + "from":"2.30", + "to":"Wisconsin" + }, + { + "from":"2.31", + "to":"Alabama" + }, + { + "from":"2.32", + "to":"North Carolina" + }, + { + "from":"2.33", + "to":"West Virginia" + }, + { + "from":"2.34", + "to":"Ohio" + }, + { + "from":"2.35", + "to":"Michigan" + }, + { + "from":"2.36", + "to":"Georgia" + }, + { + "from":"2.37", + "to":"South Carolina" + }, + { + "from":"2.38", + "to":"Virginia" + }, + { + "from":"2.39", + "to":"Pennsylvania" + }, + { + "from":"2.4", + "to":"Washington" + }, + { + "from":"2.40", + "to":"Florida" + }, + { + "from":"2.41", + "to":"District of Columbia" + }, + { + "from":"2.42", + "to":"Maryland" + }, + { + "from":"2.43", + "to":"New Jersey" + }, + { + "from":"2.44", + "to":"New York" + }, + { + "from":"2.45", + "to":"Delaware" + }, + { + "from":"2.46", + "to":"Connecticut" + }, + { + "from":"2.47", + "to":"Massachusetts" + }, + { + "from":"2.48", + "to":"Vermont" + }, + { + "from":"2.49", + "to":"Rhode Island" + }, + { + "from":"2.5", + "to":"Alaska" + }, + { + "from":"2.50", + "to":"New Hampshire" + }, + { + "from":"2.51", + "to":"Maine" + }, + { + "from":"2.6", + "to":"Arizona" + }, + { + "from":"2.7", + "to":"Utah" + }, + { + "from":"2.8", + "to":"Nevada" + }, + { + "from":"2.9", + "to":"Idaho" + } + ] + } + ] + }, + { + "type":"Column", + "row":1, + "column":1, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["A.x"], + "variables":[ + { + "name":"A.x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.x"], + "function":"A.x/120" + } + ] + }, + { + "type":"Column", + "row":1, + "column":2, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["A.y"], + "variables":[ + { + "name":"A.y", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["A.y"], + "function":"A.y/100" + } + ] + }, + { + "type":"Column", + "row":1, + "column":3, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["height"], + "variables":[ + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["PVI"], + "function":"0.6666666666666666*height" + } + ] + }, + { + "type":"Column", + "row":1, + "column":4, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["fill"], + "variables":[ + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Winner"], + "mappings":[ + { + "from":"0x6680e6ff", + "to":"Dem." + }, + { + "from":"0xcc3333ff", + "to":"Rep." + } + ] + } + ] + }, + { + "type":"Column", + "row":5, + "column":7, + "nrows":9, + "ncolumns":1, + "source":"3", + "wide":true, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Year"], + "mappings":[ + { + "from":"11.0", + "to":"'80" + }, + { + "from":"31.0", + "to":"'84" + }, + { + "from":"51.0", + "to":"'88" + }, + { + "from":"71.0", + "to":"'92" + }, + { + "from":"91.0", + "to":"'96" + }, + { + "from":"111.0", + "to":"'00" + }, + { + "from":"131.0", + "to":"'04" + }, + { + "from":"151.0", + "to":"'08" + }, + { + "from":"171.0", + "to":"'12" + } + ] + } + ] + }, + { + "type":"Column", + "row":5, + "column":8, + "nrows":9, + "ncolumns":1, + "source":"3", + "wide":true, + "network":false, + "properties":["fill"], + "variables":[ + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["More votes"], + "mappings":[ + { + "from":"0x6680e6ff", + "to":"Democrats" + }, + { + "from":"0xcc3333ff", + "to":"Republicans" + } + ] + } + ] + }, + { + "type":"Column", + "row":5, + "column":6, + "nrows":9, + "ncolumns":1, + "source":"3", + "group":"3", + "wide":false, + "network":false, + "properties":["height"], + "variables":[ + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["PVI"], + "function":"height/2" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/red-blue-america.json b/gallery/red-blue-america.json new file mode 100644 index 0000000000000000000000000000000000000000..f05874f89a3700cabf7e69332e8c10c97425d106 --- /dev/null +++ b/gallery/red-blue-america.json @@ -0,0 +1,8950 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":72.0, + "y":563.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["A.x","A.y","x","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":-40.0, + "y":41.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-5.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-7.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-18.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-29.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":5.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":2.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-2.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.0999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":700.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":18.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":18.9, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":25.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":22.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":15.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"7", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"7.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":28.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":28.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":22.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":22.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":25.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":29.099999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":33.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":30.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":33.599999999999994, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"8", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"8.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":17.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":11.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.449999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-3.1500000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"9", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"9.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":22.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":23.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":21.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":27.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":29.400000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":26.099999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":26.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"10", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"10.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-2.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"11", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"11.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":10.350000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"12", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"12.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":18.6, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":14.549999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":25.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":30.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":29.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":32.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"13", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"13.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":17.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":10.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"14", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"14.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":4.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":16.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":14.700000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"15", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"15.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":19.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":25.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":28.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"16", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"16.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":16.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":17.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":17.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"17", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"17.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":13.799999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":16.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":22.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":23.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":23.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"18", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"18.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":16.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":14.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"19", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"19.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.8999999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":15.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":17.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":20.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":15.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":15.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"20", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"20.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":17.85, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"21", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"21.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":4.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"22", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"22.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"23", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"23.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"24", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"24.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.4499999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"25", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"25.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.4499999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":11.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"26", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"26.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":12.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":17.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"27", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"27.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":15.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":19.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"28", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"28.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.8999999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":13.049999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"29", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"29.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-8.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-11.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"30", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"30.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"31", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"31.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":14.700000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":19.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":20.85, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"32", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"32.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":6.449999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":4.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"33", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"33.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"34", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"34.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"35", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"35.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-4.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"36", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"36.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-21.6, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"37", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"37.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"38", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"38.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"39", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"39.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"40", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":969.0, + "y":7.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"40.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"41", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"41.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-54.150000000000006, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-64.05000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-63.300000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-54.150000000000006, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-56.699999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-61.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-61.050000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-60.300000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"42", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"42.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-12.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-15.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"43", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"43.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.1500000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.45, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"44", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"44.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-13.049999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-18.15, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-16.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"45", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"45.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"46", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"46.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-10.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"47", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"47.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-12.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-21.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-21.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-14.549999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"48", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"48.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-23.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"49", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"49.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-14.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-12.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-22.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-20.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-16.799999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"50", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"50.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"51", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1195.0, + "y":708.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"51.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-8.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/red-blue-america.wrk b/gallery/red-blue-america.wrk new file mode 100644 index 0000000000000000000000000000000000000000..1ce8062471dbaa776efbd9b285bd89cf9d151a72 --- /dev/null +++ b/gallery/red-blue-america.wrk @@ -0,0 +1,11256 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":494.0, + "y":913.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":136.0, + "y":972.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":20.0, + "y":133.0, + "width":25.0, + "height":162.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":106.0, + "y":80.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":193.0, + "y":206.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":281.0, + "y":135.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":277.0, + "y":34.0, + "width":25.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ], + "fill":"0xcce6ffff", + "opacity":0.3015873015873016, + "coloring":"Source", + "connections":[ + { + "origin":"2", + "destination":"5", + "weight":49.0 + }, + { + "origin":"1", + "destination":"3", + "weight":81.0 + }, + { + "origin":"3", + "destination":"4", + "weight":81.0 + }, + { + "origin":"1", + "destination":"2", + "weight":81.0 + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":187.0, + "y":1123.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":23.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":22.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":23.0, + "y":0.0, + "width":22.0, + "height":98.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":46.0, + "y":0.0, + "width":22.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":69.0, + "y":0.0, + "width":22.0, + "height":131.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":92.0, + "y":0.0, + "width":22.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3b3b3ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":332.0, + "y":1116.3333333333333, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","y","shape","stroke","thickness","rotation"], + "variable":["width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["x1","x2","x3"], + ["y1","y2","y3"], + ["shape1","shape2","shape3"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Ellipse", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":180.0, + "height":180.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99cc99ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":124.0, + "height":124.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":62.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":27.0, + "y":530.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":14.0, + "common":["reference-x","reference-y","x","stroke","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["stroke1"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":690.0, + "height":286.0, + "thickness":4.4, + "rotation":0.0, + "fill":"0xf3ece5ff", + "stroke":"0x666666ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":300.0, + "width":179.0, + "height":20.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Add your Title", + "fontsize":20.0, + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Rectangle", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":266.0, + "y":1191.0, + "width":353.0, + "height":135.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xeaf0f4ff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":72.0, + "y":563.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["A.x","A.y","x","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-4.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-7.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-18.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-29.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":5.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":2.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-2.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.0999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":700.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":18.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":18.9, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":25.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":22.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":15.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"7", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"7.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":28.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":28.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":22.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":22.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":25.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":29.099999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":33.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":30.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":33.599999999999994, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"8", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"8.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":17.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":11.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.449999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-3.1500000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"9", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":120.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"9.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":22.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":23.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":21.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":27.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":29.400000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":26.099999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":26.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"10", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"10.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-2.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"10.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"11", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"11.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":10.350000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"11.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"12", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"12.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":18.6, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":11.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":14.549999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":25.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":30.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":29.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"12.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":32.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"13", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":240.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"13.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":17.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":10.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"13.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"14", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"14.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":4.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":16.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"14.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":14.700000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"15", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"15.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":19.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":25.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"15.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":28.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"16", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"16.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":16.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":17.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":17.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"16.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"17", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"17.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":13.799999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":16.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":22.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":23.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":23.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"17.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":18.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"18", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"18.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":16.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"18.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":14.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"19", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":360.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"19.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":14.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.8999999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":15.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":17.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":20.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":15.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"19.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":15.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"20", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"20.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"20.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":17.85, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"21", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"21.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":4.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"21.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":21.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"22", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"22.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"22.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"23", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"23.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"23.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"24", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":480.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"24.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.4499999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"24.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"25", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"25.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.4499999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.3500000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":11.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":13.200000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"25.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":13.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"26", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"26.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":12.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"26.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":17.700000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"27", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"27.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":15.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"27.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":19.049999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"28", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"28.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.8999999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":13.049999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"28.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"29", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"29.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-8.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-11.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"29.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"30", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":600.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"30.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"30.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"31", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"31.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":14.700000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":19.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"31.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":20.85, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"32", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"32.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":6.449999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"32.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":4.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"33", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"33.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"33.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":19.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"34", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"34.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":0.8999999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"34.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"35", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":720.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"35.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-4.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"35.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-5.699999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"36", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":100.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"36.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-21.6, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":7.050000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"36.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"37", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"37.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":12.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":11.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"37.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"38", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"38.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.3, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":8.850000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":5.550000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"38.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":0.30000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"39", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":840.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"39.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-5.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-6.1499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-3.5999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"39.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.7999999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"40", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"40.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":7.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":1.2000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":2.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"40.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"41", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":200.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"41.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-54.150000000000006, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-64.05000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-63.300000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-54.150000000000006, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-56.699999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-61.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-61.050000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"41.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-60.300000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"42", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"42.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-10.05, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-5.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-12.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"42.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-15.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"43", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"43.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.1500000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":2.55, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":3.3000000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-9.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-9.45, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"43.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"44", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":960.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"44.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-3.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-8.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-13.049999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-18.15, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-15.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"44.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-16.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"45", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":300.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"45.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-4.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.199999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-8.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-10.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"45.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-12.299999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"46", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"46.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":0.44999999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-4.65, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-10.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"46.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-10.8, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"47", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":500.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"47.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-9.149999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-9.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-12.450000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-21.299999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-21.450000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-17.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"47.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-14.549999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"48", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1080.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"48.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":3.9000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.6500000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-1.9500000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-6.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-11.399999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-10.2, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-12.600000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-20.1, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"48.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-23.549999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"49", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":400.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"49.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-14.100000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":-12.899999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-13.649999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-22.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-20.4, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-16.799999999999997, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"49.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-16.950000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"50", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":600.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"50.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":13.950000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":16.200000000000003, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":14.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":9.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":1.35, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":0.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-0.75, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-2.4000000000000004, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"50.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"51", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1200.0, + "y":708.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":11.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"51.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":11.0, + "height":-1.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":11.0, + "height":-1.0499999999999998, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":22.0, + "y":0.0, + "width":11.0, + "height":2.8499999999999996, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":33.0, + "y":0.0, + "width":11.0, + "height":-0.6000000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":11.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":55.0, + "y":0.0, + "width":11.0, + "height":-7.800000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":66.0, + "y":0.0, + "width":11.0, + "height":-6.300000000000001, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":77.0, + "y":0.0, + "width":11.0, + "height":-8.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"51.9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":88.0, + "y":0.0, + "width":11.0, + "height":-8.25, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":322.0, + "y":1269.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":20.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":11.0, + "y":0.0, + "width":19.0, + "height":-10.95, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":31.0, + "y":0.0, + "width":19.0, + "height":-31.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":51.0, + "y":0.0, + "width":19.0, + "height":-7.6499999999999995, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.0, + "y":0.0, + "width":19.0, + "height":-7.949999999999999, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":91.0, + "y":0.0, + "width":19.0, + "height":-6.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"6", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":111.0, + "y":0.0, + "width":19.0, + "height":-0.15000000000000002, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"7", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":131.0, + "y":0.0, + "width":19.0, + "height":6.6000000000000005, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"8", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":151.0, + "y":0.0, + "width":19.0, + "height":11.7, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"9", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.0, + "y":0.0, + "width":19.0, + "height":35.5, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcc3333ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "datasheet":[ + { + "type":"Column", + "row":1, + "column":0, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["A.id"], + "variables":[ + { + "name":"A.id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["State"], + "mappings":[ + { + "from":"2.1", + "to":"Hawaii" + }, + { + "from":"2.10", + "to":"New Mexico" + }, + { + "from":"2.11", + "to":"Colorado" + }, + { + "from":"2.12", + "to":"Wyoming" + }, + { + "from":"2.13", + "to":"Montana" + }, + { + "from":"2.14", + "to":"Texas" + }, + { + "from":"2.15", + "to":"Oklahoma" + }, + { + "from":"2.16", + "to":"Kansas" + }, + { + "from":"2.17", + "to":"Nebraska" + }, + { + "from":"2.18", + "to":"South Dakota" + }, + { + "from":"2.19", + "to":"North Dakota" + }, + { + "from":"2.2", + "to":"California" + }, + { + "from":"2.20", + "to":"Louisiana" + }, + { + "from":"2.21", + "to":"Arkansas" + }, + { + "from":"2.22", + "to":"Missouri" + }, + { + "from":"2.23", + "to":"Iowa" + }, + { + "from":"2.24", + "to":"Minnesota" + }, + { + "from":"2.25", + "to":"Mississippi" + }, + { + "from":"2.26", + "to":"Tennessee" + }, + { + "from":"2.27", + "to":"Kentucky" + }, + { + "from":"2.28", + "to":"Indiana" + }, + { + "from":"2.29", + "to":"Illinois" + }, + { + "from":"2.3", + "to":"Oregon" + }, + { + "from":"2.30", + "to":"Wisconsin" + }, + { + "from":"2.31", + "to":"Alabama" + }, + { + "from":"2.32", + "to":"North Carolina" + }, + { + "from":"2.33", + "to":"West Virginia" + }, + { + "from":"2.34", + "to":"Ohio" + }, + { + "from":"2.35", + "to":"Michigan" + }, + { + "from":"2.36", + "to":"Georgia" + }, + { + "from":"2.37", + "to":"South Carolina" + }, + { + "from":"2.38", + "to":"Virginia" + }, + { + "from":"2.39", + "to":"Pennsylvania" + }, + { + "from":"2.4", + "to":"Washington" + }, + { + "from":"2.40", + "to":"Florida" + }, + { + "from":"2.41", + "to":"District of Columbia" + }, + { + "from":"2.42", + "to":"Maryland" + }, + { + "from":"2.43", + "to":"New Jersey" + }, + { + "from":"2.44", + "to":"New York" + }, + { + "from":"2.45", + "to":"Delaware" + }, + { + "from":"2.46", + "to":"Connecticut" + }, + { + "from":"2.47", + "to":"Massachusetts" + }, + { + "from":"2.48", + "to":"Vermont" + }, + { + "from":"2.49", + "to":"Rhode Island" + }, + { + "from":"2.5", + "to":"Alaska" + }, + { + "from":"2.50", + "to":"New Hampshire" + }, + { + "from":"2.51", + "to":"Maine" + }, + { + "from":"2.6", + "to":"Arizona" + }, + { + "from":"2.7", + "to":"Utah" + }, + { + "from":"2.8", + "to":"Nevada" + }, + { + "from":"2.9", + "to":"Idaho" + } + ] + } + ] + }, + { + "type":"Column", + "row":1, + "column":1, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["A.x"], + "variables":[ + { + "name":"A.x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["GridX"], + "function":"A.x/120" + } + ] + }, + { + "type":"Column", + "row":1, + "column":2, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["A.y"], + "variables":[ + { + "name":"A.y", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["GridY"], + "function":"A.y/100" + } + ] + }, + { + "type":"Column", + "row":1, + "column":3, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["height"], + "variables":[ + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["PVI"], + "function":"0.6666666666666666*height" + } + ] + }, + { + "type":"Column", + "row":1, + "column":4, + "nrows":459, + "ncolumns":1, + "source":"2", + "wide":false, + "network":false, + "properties":["fill"], + "variables":[ + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Winner"], + "mappings":[ + { + "from":"0x6680e6ff", + "to":"Dem." + }, + { + "from":"0xcc3333ff", + "to":"Rep." + } + ] + } + ] + }, + { + "type":"Column", + "row":5, + "column":7, + "nrows":9, + "ncolumns":1, + "source":"3", + "wide":true, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Symbolic", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["Year"], + "mappings":[ + { + "from":"11.0", + "to":"'80" + }, + { + "from":"31.0", + "to":"'84" + }, + { + "from":"51.0", + "to":"'88" + }, + { + "from":"71.0", + "to":"'92" + }, + { + "from":"91.0", + "to":"'96" + }, + { + "from":"111.0", + "to":"'00" + }, + { + "from":"131.0", + "to":"'04" + }, + { + "from":"151.0", + "to":"'08" + }, + { + "from":"171.0", + "to":"'12" + } + ] + } + ] + }, + { + "type":"Column", + "row":5, + "column":8, + "nrows":9, + "ncolumns":1, + "source":"3", + "wide":true, + "network":false, + "properties":["fill"], + "variables":[ + { + "name":"fill", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":true, + "node":false, + "labels":["More votes"], + "mappings":[ + { + "from":"0x6680e6ff", + "to":"Democrats" + }, + { + "from":"0xcc3333ff", + "to":"Republicans" + } + ] + } + ] + }, + { + "type":"Column", + "row":5, + "column":6, + "nrows":9, + "ncolumns":1, + "source":"3", + "wide":false, + "network":false, + "properties":["height"], + "variables":[ + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":false, + "labels":["PVI"], + "function":"height/2" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/republicans-democrats-2.json b/gallery/republicans-democrats-2.json new file mode 100644 index 0000000000000000000000000000000000000000..0c6b1ccd7c11480d7534561720a45cfc466d68bd --- /dev/null +++ b/gallery/republicans-democrats-2.json @@ -0,0 +1,850 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":644.0, + "y":1000.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":54.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":28.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":18.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":82.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":24.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":136.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":92.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":136.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":190.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":6.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":160.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":244.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":178.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":292.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":298.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":228.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":398.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":54.0, + "y":999.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":37.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","fill1","rotation1","x2","width2","fill2","x3","fill3"], + "variable":["A.y","text1","reference-x2","reference-y2","y2","height2","text2","fontsize","rotation2","reference-x3","reference-y3","y3","width3","height3","text3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":22.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Stated support for gun rights", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"9", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"0", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":78.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Criticized Obama Administration", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"12", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"0", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":134.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"3.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Praised first responders", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"68", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"46", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":0.0, + "y":190.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"4.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Used phrases similar to radical Islam", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"4.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"80", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"4.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"3", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"5", + "level":1, + "prefix":"A.", + "x":0.0, + "y":246.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"5.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Condemned terrorism", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"5.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"146", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"5.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"89", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"6", + "level":1, + "prefix":"A.", + "x":0.0, + "y":302.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"6.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Thoughs & Prayers", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"6.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"199", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"6.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"114", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/republicans-democrats-2.wrk b/gallery/republicans-democrats-2.wrk new file mode 100644 index 0000000000000000000000000000000000000000..97bb136c24e65ef0b585190e3b0e2d099a39188e --- /dev/null +++ b/gallery/republicans-democrats-2.wrk @@ -0,0 +1,2835 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":490.0, + "y":937.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1004.0, + "y":935.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":-72.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":-82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":-89.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":-93.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":-51.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":-26.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":30.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":86.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":103.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":125.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":66.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":36.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":9.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":125.0, + "y":1120.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":270.5, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":50.0, + "width":37.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":222.5, + "y":1071.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","stroke","thickness","rotation"], + "variable":["y","width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":88.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":16.0, + "width":16.0, + "height":91.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":586.0, + "y":1001.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":54.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":28.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":18.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":82.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":24.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":136.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":92.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":136.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":190.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":6.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":160.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":244.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":178.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":292.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":298.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":228.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":398.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":54.0, + "y":999.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":35.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","fill1","rotation1","x2","width2","fill2","x3","fill3"], + "variable":["A.y","text1","reference-x2","reference-y2","y2","height2","text2","fontsize","rotation2","reference-x3","reference-y3","y3","width3","height3","text3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":27.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Stated support for gun rights", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"9", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"0", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":81.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Criticized Obama Administration", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"12", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"0", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":135.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"3.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Praised first responders", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"68", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"46", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":0.0, + "y":189.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"4.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Used phrases similar to radical Islam", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"4.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"80", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"4.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"3", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"5", + "level":1, + "prefix":"A.", + "x":0.0, + "y":243.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"5.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Condemned terrorism", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"5.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"146", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"5.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"89", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"6", + "level":1, + "prefix":"A.", + "x":0.0, + "y":297.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"6.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Thoughs & Prayers", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"6.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"199", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"6.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"114", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":29.0, + "y":1344.0, + "width":482.0, + "height":22.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "text":"Topics Mentioned More by Republicans", + "fontsize":20.0, + "lock":"false" + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":581.0, + "y":741.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":54.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccff6c", + "x":0.0, + "y":26.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":158.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccff6c", + "x":0.0, + "y":80.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":188.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":48.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccff6c", + "x":0.0, + "y":134.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":246.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":38.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":51.0, + "y":741.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":53.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","fill1","rotation1","x2","width2","fill2","x3","fill3"], + "variable":["A.y","text1","reference-x2","reference-y2","y2","height2","text2","fontsize","rotation2","reference-x3","reference-y3","y3","width3","height3","text3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":27.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Advanced hun control", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"0", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"79", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":80.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Mentioned hate", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"24", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"94", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":133.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"3.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Addressed LGBT community", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"19", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"123", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":24.0, + "y":912.0, + "width":482.0, + "height":22.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"Topics Mentioned More by Democrats", + "fontsize":20.0, + "lock":"false" + } + ], + "datasheet":[ + { + "type":"Table", + "row":2, + "column":0, + "nrows":6, + "ncolumns":3, + "source":"2", + "group":"2", + "wide":true, + "network":false, + "properties":["text1","text2","text3"], + "variables":[ + { + "name":"text1", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Topic"], + "function":"text1" + }, + { + "name":"text2", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Rep."], + "function":"text2" + }, + { + "name":"text3", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Dem."], + "function":"text3" + } + ] + }, + { + "type":"Table", + "row":11, + "column":0, + "nrows":6, + "ncolumns":3, + "source":"1", + "group":"1", + "wide":true, + "network":false, + "properties":["A.y","x"], + "variables":[ + { + "name":"A.y", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Topic"], + "mappings":[ + { + "from":"28.0", + "to":"Stated support for gun rights" + }, + { + "from":"82.0", + "to":"Criticized Obama Administration" + }, + { + "from":"136.0", + "to":"Praised first responders" + }, + { + "from":"190.0", + "to":"Used phrases similar to radical Islam" + }, + { + "from":"244.0", + "to":"Condemned terrorism" + }, + { + "from":"298.0", + "to":"Thoughs & Prayers" + } + ] + }, + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":true, + "labels":["Responders","Responders"], + "function":"x/2" + } + ] + }, + { + "type":"Value", + "row":0, + "column":0, + "nrows":1, + "ncolumns":2, + "source":"3", + "wide":true, + "network":false, + "properties":["text"], + "variables":[ + { + "name":"text", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Title 1"], + "function":"text" + } + ] + }, + { + "type":"Value", + "row":0, + "column":0, + "nrows":1, + "ncolumns":2, + "source":"3", + "wide":true, + "network":false, + "properties":["text"], + "variables":[ + { + "name":"text", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Title 1"], + "function":"text" + } + ] + }, + { + "type":"Value", + "row":19, + "column":0, + "nrows":1, + "ncolumns":2, + "source":"6", + "wide":true, + "network":false, + "properties":["text"], + "variables":[ + { + "name":"text", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Title 2"], + "function":"text" + } + ] + }, + { + "type":"Table", + "row":21, + "column":0, + "nrows":3, + "ncolumns":3, + "source":"5", + "group":"5", + "wide":true, + "network":false, + "properties":["text1","text2","text3"], + "variables":[ + { + "name":"text1", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Topic"], + "function":"text1" + }, + { + "name":"text2", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Rep."], + "function":"text2" + }, + { + "name":"text3", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Dem."], + "function":"text3" + } + ] + }, + { + "type":"Table", + "row":26, + "column":0, + "nrows":3, + "ncolumns":3, + "source":"4", + "group":"4", + "wide":true, + "network":false, + "properties":["A.y","x"], + "variables":[ + { + "name":"A.y", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Topic"], + "mappings":[ + { + "from":"26.0", + "to":"Advanced hun control" + }, + { + "from":"80.0", + "to":"Mentioned hate" + }, + { + "from":"134.0", + "to":"Addressed LGBT community" + } + ] + }, + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":true, + "labels":["Responders","Responders"], + "function":"x/2" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/republicans-democrats.json b/gallery/republicans-democrats.json new file mode 100644 index 0000000000000000000000000000000000000000..c6636e230372a0087f9d13958f0052be54479fd2 --- /dev/null +++ b/gallery/republicans-democrats.json @@ -0,0 +1,573 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":585.0, + "y":773.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.x","B.curve","B.stroke","B.thickness","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.y","A.y","A.stroke","x"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":22.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":57.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"1.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccffbc", + "x":0.0, + "y":23.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":158.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"1.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccffbc", + "x":0.0, + "y":80.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":188.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":48.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"1.3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccffbc", + "x":0.0, + "y":137.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":246.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":38.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":22.0, + "y":225.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":57.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"2.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":18.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":18.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":75.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":24.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":92.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":136.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":189.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":6.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":160.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":246.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":178.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":292.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":303.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":228.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":398.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/republicans-democrats.wrk b/gallery/republicans-democrats.wrk new file mode 100644 index 0000000000000000000000000000000000000000..f8442676571419a211e9f211dd15874417338d10 --- /dev/null +++ b/gallery/republicans-democrats.wrk @@ -0,0 +1,2801 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":490.0, + "y":937.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1004.0, + "y":935.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":-72.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":-82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":-89.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":-93.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":-51.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":-26.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":30.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":86.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":103.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":125.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":66.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":36.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":9.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":125.0, + "y":1120.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":270.5, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":50.0, + "width":37.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":222.5, + "y":1071.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","stroke","thickness","rotation"], + "variable":["y","width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":88.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":16.0, + "width":16.0, + "height":91.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":59.0, + "y":997.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":37.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","fill1","rotation1","x2","width2","fill2","x3","fill3"], + "variable":["A.y","text1","reference-x2","reference-y2","y2","height2","text2","reference-x3","reference-y3","y3","width3","height3","text3","fontsize","rotation2","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":22.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Stated support for gun rights", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"9", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"0", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":78.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Criticized Obama Administration", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"12", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"0", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":134.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"3.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Praised first responders", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"68", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"46", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"4", + "level":1, + "prefix":"A.", + "x":0.0, + "y":190.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"4.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Used phrases similar to radical Islam", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"4.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"80", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"4.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"3", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"5", + "level":1, + "prefix":"A.", + "x":0.0, + "y":246.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"5.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Condemned terrorism", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"5.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"146", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"5.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"89", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"6", + "level":1, + "prefix":"A.", + "x":0.0, + "y":302.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"6.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Thoughs & Prayers", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"6.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"199", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"6.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"114", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":55.0, + "y":772.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":37.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.rotation","reference-x1","reference-y1","x1","y1","width1","height1","fill1","rotation1","x2","width2","fill2","x3","fill3"], + "variable":["A.y","text1","reference-x2","reference-y2","y2","height2","text2","fontsize","rotation2","reference-x3","reference-y3","y3","width3","height3","text3","rotation3"], + "components":[ + { + "type":"Group", + "id":"1", + "level":1, + "prefix":"A.", + "x":0.0, + "y":25.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3","fontsize","fontsize"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Advanced gun control", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"0", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"79", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"2", + "level":1, + "prefix":"A.", + "x":0.0, + "y":81.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3","fontsize","fontsize"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Mentioned hate", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"24", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"94", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Group", + "id":"3", + "level":1, + "prefix":"A.", + "x":0.0, + "y":137.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"Spacing", + "distribution-y":"None", + "rotation":0.0, + "delta-x":10.0, + "delta-y":0.0, + "common":["reference-x","reference-y","y","width","height","fontsize","rotation"], + "variable":["x","text","fill"], + "public":["text1","text2","text3","fontsize","fontsize"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1","reference-y2","reference-y3"], + ["y1","y2","y3"], + ["width1"], + ["width2","width3"], + ["height1","height2","height3"], + ["fontsize","fontsize","fontsize"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Text", + "id":"3.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":368.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Addressed LGBT community", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":378.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "text":"19", + "fontsize":14.0, + "lock":"false" + }, + { + "type":"Text", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":438.0, + "y":0.0, + "width":50.0, + "height":19.0, + "rotation":0.0, + "fill":"0x6680e6ff", + "text":"123", + "fontsize":14.0, + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":3, + "prefix":"C.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":585.0, + "y":773.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["B.sticky-x","B.sticky-y","B.distribution-x","B.delta-x","B.distribution-y","B.delta-y","B.x","B.curve","B.stroke","B.thickness","B.rotation","A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["B.y","A.y","A.stroke","x"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":22.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":57.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"1.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccffbc", + "x":0.0, + "y":23.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":158.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"1.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccffbc", + "x":0.0, + "y":80.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":188.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":48.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"1.3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xb3ccffbc", + "x":0.0, + "y":137.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":246.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":38.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":22.0, + "y":225.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":57.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x"], + "components":[ + { + "type":"LineChart", + "id":"2.1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":18.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":18.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":75.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":24.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":92.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":136.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":189.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":6.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":160.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":246.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":178.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":292.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"LineChart", + "id":"2.6", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xe699b345", + "x":0.0, + "y":303.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"StraightSolid", + "common":["reference-x","reference-y","y","width","height","shape","stroke","thickness","rotation"], + "variable":["x","fill"], + "components":[ + { + "type":"Ellipse", + "id":"2.6.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":228.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":398.0, + "y":0.0, + "width":17.0, + "height":17.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":44.0, + "y":1342.0, + "width":398.0, + "height":23.0, + "rotation":0.0, + "fill":"0xcc3333ff", + "text":"Topics Mentioned More by Republicans", + "fontsize":20.0, + "lock":"false" + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":40.0, + "y":944.0, + "width":398.0, + "height":23.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "text":"Topics Mentioned More by Democrats", + "fontsize":20.0, + "lock":"false" + } + ], + "datasheet":[ + { + "type":"Value", + "row":0, + "column":0, + "nrows":1, + "ncolumns":2, + "source":"4", + "wide":true, + "network":false, + "properties":["text"], + "variables":[ + { + "name":"text", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Title 1"], + "function":"text" + } + ] + }, + { + "type":"Table", + "row":2, + "column":0, + "nrows":6, + "ncolumns":3, + "source":"1", + "group":"1", + "wide":true, + "network":false, + "properties":["text1","text2","text3"], + "variables":[ + { + "name":"text1", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Topic"], + "function":"text1" + }, + { + "name":"text2", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Rep."], + "function":"text2" + }, + { + "name":"text3", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Dem."], + "function":"text3" + } + ] + }, + { + "type":"Value", + "row":10, + "column":0, + "nrows":1, + "ncolumns":2, + "source":"5", + "wide":true, + "network":false, + "properties":["text"], + "variables":[ + { + "name":"text", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Title 2"], + "function":"text" + } + ] + }, + { + "type":"Table", + "row":12, + "column":0, + "nrows":3, + "ncolumns":3, + "source":"2", + "group":"2", + "wide":true, + "network":false, + "properties":["text1","text2","text3"], + "variables":[ + { + "name":"text1", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Topic"], + "function":"text1" + }, + { + "name":"text2", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Rep."], + "function":"text2" + }, + { + "name":"text3", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Dem."], + "function":"text3" + } + ] + }, + { + "type":"Column", + "row":17, + "column":0, + "nrows":3, + "ncolumns":2, + "source":"3.1", + "group":"3.1", + "wide":true, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Dem.","Rep."], + "function":"x/2" + } + ] + }, + { + "type":"Column", + "row":21, + "column":0, + "nrows":6, + "ncolumns":2, + "source":"3.2", + "group":"3.2", + "wide":true, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Dem.","Rep."], + "function":"x/2" + } + ] + }, + { + "type":"Column", + "row":14, + "column":4, + "nrows":18, + "ncolumns":1, + "source":"3", + "group":"3", + "wide":false, + "network":false, + "properties":["x"], + "variables":[ + { + "name":"x", + "type":"Functional", + "axis":false, + "axis-outer":true, + "legend":false, + "node":true, + "labels":["Mentions"], + "function":"x/2" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/gallery/sankey-UK-elections.json b/gallery/sankey-UK-elections.json new file mode 100644 index 0000000000000000000000000000000000000000..b0ed617bc8902aa36e32fd50742d675427f72319 --- /dev/null +++ b/gallery/sankey-UK-elections.json @@ -0,0 +1,270 @@ +{ + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":98.0, + "y":903.5, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":23.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":51.0, + "width":16.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":68.0, + "width":16.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":223.0, + "width":16.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":406.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":49.0, + "width":16.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":74.0, + "width":16.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":227.0, + "width":16.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3253968253968254, + "coloring":"Source", + "connections":[ + { + "origin":"1.1", + "destination":"2.2", + "weight":6.3953488372093 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":2.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":30.0 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":138.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + } + ] + } + ] +} \ No newline at end of file diff --git a/gallery/sankey-UK-elections.wrk b/gallery/sankey-UK-elections.wrk new file mode 100644 index 0000000000000000000000000000000000000000..ec69192d9052aa21d51a126b54dad9d5da1d9edb --- /dev/null +++ b/gallery/sankey-UK-elections.wrk @@ -0,0 +1,1681 @@ +{ + "workspace": { + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":490.0, + "y":937.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":1004.0, + "y":1228.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "common":["reference-x","reference-y","x","rotation"], + "variable":["y","width","height","shape","text","fontsize","fill","stroke","thickness"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":66.0, + "height":62.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Text", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":72.0, + "width":75.0, + "height":19.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"my label", + "fontsize":14.0, + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":1004.0, + "y":935.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","height","shape","stroke","thickness","rotation"], + "variable":["y","width","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":-72.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":-82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":-89.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":-93.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":-51.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":-39.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":-26.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6b3e6ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":11.0, + "curve":"None", + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":30.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":36.0, + "width":86.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":72.0, + "width":82.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":108.0, + "width":103.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":144.0, + "width":125.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.6", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":180.0, + "width":66.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.7", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":216.0, + "width":36.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.8", + "level":0, + "reference-x":"Left", + "reference-y":"Center", + "x":0.0, + "y":252.0, + "width":9.0, + "height":25.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x99b3ffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":978.0, + "y":803.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":-124.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x1","y","stroke","thickness","rotation"], + "variable":["width","height","shape","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["y1","y2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":106.0, + "height":106.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":0.0, + "width":78.0, + "height":78.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffff66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":125.0, + "y":1120.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":270.5, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":23.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":50.0, + "width":37.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":222.5, + "y":1071.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":4.0, + "common":["reference-x","reference-y","x","shape","stroke","thickness","rotation"], + "variable":["y","width","height","fill"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2"], + ["reference-y1","reference-y2"], + ["x1","x2"], + ["shape1","shape2"], + ["stroke1","stroke2"], + ["thickness1","thickness2"], + ["rotation1","rotation2"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":88.0, + "height":12.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":16.0, + "width":16.0, + "height":91.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "visualizations":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":98.0, + "y":903.5, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":23.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":44.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":51.0, + "width":16.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":68.0, + "width":16.0, + "height":148.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":223.0, + "width":16.0, + "height":96.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":406.0, + "y":14.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":7.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":16.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":49.0, + "width":16.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe666ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":74.0, + "width":16.0, + "height":146.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":227.0, + "width":16.0, + "height":92.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e6ff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ], + "fill":"0xaaaabcff", + "opacity":0.3253968253968254, + "coloring":"Source", + "connections":[ + { + "origin":"1.1", + "destination":"2.2", + "weight":6.3953488372093 + }, + { + "origin":"1.4", + "destination":"2.2", + "weight":2.0 + }, + { + "origin":"1.1", + "destination":"2.1", + "weight":30.0 + }, + { + "origin":"1.3", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.2", + "destination":"2.2", + "weight":10.0 + }, + { + "origin":"1.4", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.4", + "destination":"2.4", + "weight":84.0 + }, + { + "origin":"1.4", + "destination":"2.1", + "weight":6.0 + }, + { + "origin":"1.1", + "destination":"2.4", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.3", + "weight":138.0 + }, + { + "origin":"1.1", + "destination":"2.3", + "weight":4.0 + }, + { + "origin":"1.3", + "destination":"2.4", + "weight":4.0 + } + ] + }, + { + "type":"Text", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":84.0, + "y":1283.0, + "width":314.0, + "height":30.0, + "rotation":0.0, + "fill":"0x696969ff", + "text":"Change in council control", + "fontsize":22.0, + "lock":"false" + } + ], + "datasheet":[ + { + "type":"Table", + "row":1, + "column":0, + "nrows":12, + "ncolumns":3, + "source":"1", + "group":"1", + "wide":true, + "network":true, + "properties":["id","id","weight"], + "variables":[ + { + "name":"id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["source","destination"], + "mappings":[ + { + "from":"1.1.1", + "to":"No control" + }, + { + "from":"1.1.2", + "to":"Lib Dems" + }, + { + "from":"1.1.3", + "to":"Labour" + }, + { + "from":"1.1.4", + "to":"Conservative" + }, + { + "from":"1.2.1", + "to":"No control" + }, + { + "from":"1.2.2", + "to":"Lib Dems" + }, + { + "from":"1.2.3", + "to":"Labour" + }, + { + "from":"1.2.4", + "to":"Conservative" + } + ] + }, + { + "name":"weight", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["Change (seats)"], + "function":"weight/2" + } + ] + }, + { + "type":"Column", + "row":15, + "column":0, + "nrows":8, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["id"], + "variables":[ + { + "name":"id", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":false, + "labels":["id"], + "function":"id" + } + ] + }, + { + "type":"Column", + "row":15, + "column":1, + "nrows":8, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["height"], + "variables":[ + { + "name":"height", + "type":"Functional", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["Seats"], + "function":"height/2" + } + ] + }, + { + "type":"Column", + "row":15, + "column":2, + "nrows":8, + "ncolumns":1, + "source":"1", + "group":"1", + "wide":false, + "network":false, + "properties":["A.id"], + "variables":[ + { + "name":"A.id", + "type":"Symbolic", + "axis":false, + "axis-outer":false, + "legend":false, + "node":true, + "labels":["When"], + "mappings":[ + { + "from":"1.1", + "to":"Before 2018 election" + }, + { + "from":"1.2", + "to":"After 2018 election" + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/lib/controlsfx-8.40.12-sources/META-INF/MANIFEST.MF b/lib/controlsfx-8.40.12-sources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..58630c02ef423cffd6dd6aafd946eb8512040c37 --- /dev/null +++ b/lib/controlsfx-8.40.12-sources/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/lib/controlsfx-8.40.12-sources/META-INF/services/org.controlsfx.glyphfont.GlyphFont b/lib/controlsfx-8.40.12-sources/META-INF/services/org.controlsfx.glyphfont.GlyphFont new file mode 100644 index 0000000000000000000000000000000000000000..1d1eed76422a4f8a82d07ad77ca70ed048b2d1b5 --- /dev/null +++ b/lib/controlsfx-8.40.12-sources/META-INF/services/org.controlsfx.glyphfont.GlyphFont @@ -0,0 +1 @@ +org.controlsfx.glyphfont.FontAwesome \ No newline at end of file diff --git a/lib/matheclipse-core-1.0.0-SNAPSHOT-jar-with-dependencies.jar b/lib/matheclipse-core-1.0.0-SNAPSHOT-jar-with-dependencies.jar new file mode 100644 index 0000000000000000000000000000000000000000..37cb8cefa916e9ee55bb8f474ec40035aa60008c Binary files /dev/null and b/lib/matheclipse-core-1.0.0-SNAPSHOT-jar-with-dependencies.jar differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..0ad7dae2e580a28513e7852db7dacddaa81de0d4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,44 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>Infographer</groupId> + <artifactId>Infographer</artifactId> + <version>0.0.1-SNAPSHOT</version> + <build> + <sourceDirectory>src</sourceDirectory> + <resources> + <resource> + <directory>src</directory> + <excludes> + <exclude>**/*.java</exclude> + </excludes> + </resource> + </resources> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.7.0</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + + + <dependencies> + <dependency> + <groupId>javax.json</groupId> + <artifactId>javax.json-api</artifactId> + <version>1.0</version> + </dependency> + + <dependency> + <groupId>org.glassfish</groupId> + <artifactId>javax.json</artifactId> + <version>1.0.4</version> + </dependency> + + </dependencies> + +</project> \ No newline at end of file diff --git a/sessions/library.json b/sessions/library.json new file mode 100644 index 0000000000000000000000000000000000000000..b3953ca72d06cfd6a7dbbe5ffa5cc47e67118be1 --- /dev/null +++ b/sessions/library.json @@ -0,0 +1,1899 @@ +{ + "library":[ + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":96.875, + "y":785.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":57.125, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","x","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","y","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":42.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":54.0, + "width":37.0, + "height":27.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":86.0, + "width":37.0, + "height":38.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":99.125, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":156.25, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":57.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":62.0, + "width":37.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":87.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":213.375, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":47.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":52.0, + "width":37.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":99.0, + "width":37.0, + "height":54.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":90.0, + "y":871.0, + "sticky-y":"Yes", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":10.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","shape","stroke","thickness","rotation"], + "variable":["x","y","width","height","fill"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":125.0, + "y":141.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":88.0, + "y":76.0, + "width":18.0, + "height":18.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":116.0, + "y":36.0, + "width":16.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":206.0, + "y":69.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":186.0, + "y":114.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":266.0, + "y":107.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":216.0, + "y":141.0, + "width":14.0, + "height":14.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"1.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":300.0, + "y":188.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe64d4db1", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":0.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","width","height"], + "components":[ + { + "type":"Ellipse", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":30.0, + "y":56.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":67.0, + "y":104.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":126.0, + "y":68.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":164.0, + "y":71.0, + "width":26.0, + "height":26.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":147.0, + "y":116.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.6", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":257.0, + "y":157.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.7", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":311.0, + "y":149.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + }, + { + "type":"Ellipse", + "id":"2.8", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":237.0, + "y":97.0, + "width":20.0, + "height":20.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x6680e696", + "stroke":"0x50508000", + "shape":"Ellipse", + "lock":"true" + } + ] + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":143.75, + "y":808.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"None", + "distribution-y":"Distance", + "rotation":0.0, + "delta-x":0.0, + "delta-y":44.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.x","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.y","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":44.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":16.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":88.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":24.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":132.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":30.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"5", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":29.0, + "y":176.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":50.25, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","height","shape","fill","stroke","thickness","rotation"], + "variable":["x"], + "components":[ + { + "type":"Rectangle", + "id":"5.1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":21.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":71.25, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":121.5, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":171.75, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5.5", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":222.0, + "y":0.0, + "width":30.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xcbd7e4ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":122.0, + "y":908.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":29.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":15.0, + "y":0.0, + "width":28.0, + "height":85.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xccccccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":44.0, + "y":0.0, + "width":28.0, + "height":129.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x808080ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":73.0, + "y":0.0, + "width":28.0, + "height":42.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e64dff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":102.0, + "y":0.0, + "width":28.0, + "height":68.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x80b380ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Group", + "level":1, + "prefix":"A.", + "x":494.0, + "y":913.0, + "sticky-y":"Yes", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":20.0, + "delta-x":0.0, + "delta-y":0.0, + "common":["reference-x","reference-y","x","height","shape","fill","stroke","thickness","rotation"], + "variable":["y","width"], + "public":["A.x","A.y"], + "bindings":[ + ["reference-x1","reference-x2","reference-x3"], + ["reference-y1"], + ["reference-y2","reference-y3"], + ["x1","x2","x3"], + ["height1","height3"], + ["height2"], + ["shape1","shape2","shape3"], + ["fill1","fill3"], + ["fill2"], + ["stroke1","stroke2","stroke3"], + ["thickness1","thickness2","thickness3"], + ["rotation1","rotation2","rotation3"] + ], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Top", + "x":0.0, + "y":-41.0, + "width":92.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":2.0, + "width":13.0, + "height":86.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":0.0, + "y":50.0, + "width":55.0, + "height":10.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xe6e699ff", + "stroke":"0x505080ff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0x808080ff", + "x":136.0, + "y":972.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"None", + "rotation":0.0, + "delta-x":0.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","width","shape","fill","stroke","thickness","rotation"], + "variable":["x","y","height"], + "components":[ + { + "type":"Rectangle", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":20.0, + "y":133.0, + "width":25.0, + "height":162.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":106.0, + "y":80.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":193.0, + "y":206.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":281.0, + "y":135.0, + "width":25.0, + "height":81.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"5", + "level":0, + "reference-x":"Center", + "reference-y":"Center", + "x":277.0, + "y":34.0, + "width":25.0, + "height":49.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0x50508000", + "shape":"Rectangle", + "lock":"false" + } + ], + "fill":"0xcce6ffff", + "opacity":0.3015873015873016, + "coloring":"Source", + "connections":[ + { + "origin":"1", + "destination":"2", + "weight":81.0 + }, + { + "origin":"3", + "destination":"4", + "weight":81.0 + }, + { + "origin":"1", + "destination":"3", + "weight":81.0 + }, + { + "origin":"2", + "destination":"5", + "weight":49.0 + } + ] + }, + { + "type":"Collection", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":107.125, + "y":3.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"None", + "distribution-y":"Spacing", + "rotation":0.0, + "delta-x":0.0, + "delta-y":5.0, + "curve":"None", + "common":["reference-x","reference-y","x","width","shape","stroke","thickness","rotation"], + "variable":["y","height","fill"], + "components":[ + { + "type":"Ellipse", + "id":"1", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":37.0, + "height":29.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffe6ccff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"2", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":34.0, + "width":37.0, + "height":22.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xff9999ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + }, + { + "type":"Ellipse", + "id":"3", + "level":0, + "reference-x":"Center", + "reference-y":"Bottom", + "x":0.0, + "y":61.0, + "width":37.0, + "height":37.0, + "thickness":0.5, + "rotation":0.0, + "fill":"0xffcc66ff", + "stroke":"0x505080ff", + "shape":"Ellipse", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "level":2, + "prefix":"B.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":114.0, + "y":996.0, + "sticky-y":"No", + "sticky-x":"No", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":222.0, + "delta-y":0.0, + "curve":"None", + "common":["A.sticky-x","A.sticky-y","A.distribution-x","A.delta-x","A.distribution-y","A.delta-y","A.y","A.curve","A.stroke","A.thickness","A.rotation","reference-x","reference-y","y","width","shape","fill","stroke","thickness","rotation"], + "variable":["A.x","x","height"], + "components":[ + { + "type":"Collection", + "id":"1", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":25.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":56.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"1.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":56.0, + "height":-48.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":56.0, + "y":0.0, + "width":56.0, + "height":90.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"1.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":112.0, + "y":0.0, + "width":56.0, + "height":180.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"2", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":247.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":56.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"2.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":56.0, + "height":-24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":56.0, + "y":0.0, + "width":56.0, + "height":90.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"2.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":112.0, + "y":0.0, + "width":56.0, + "height":150.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"3", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":469.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":56.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"3.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":56.0, + "height":-24.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":56.0, + "y":0.0, + "width":56.0, + "height":36.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"3.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":112.0, + "y":0.0, + "width":56.0, + "height":42.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + } + ] + }, + { + "type":"Collection", + "id":"4", + "level":1, + "prefix":"A.", + "thickness":1.5, + "stroke":"0xd9cccc80", + "x":691.0, + "y":0.0, + "sticky-y":"No", + "sticky-x":"Yes", + "distribution-x":"Distance", + "distribution-y":"None", + "rotation":0.0, + "delta-x":56.0, + "delta-y":0.0, + "curve":"None", + "common":["reference-x","reference-y","y","width","shape","stroke","thickness","rotation"], + "variable":["x","height","fill"], + "components":[ + { + "type":"Rectangle", + "id":"4.1", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":0.0, + "y":0.0, + "width":56.0, + "height":-12.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x4d66ccff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.2", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":56.0, + "y":0.0, + "width":56.0, + "height":90.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0xb3ccffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + }, + { + "type":"Rectangle", + "id":"4.3", + "level":0, + "reference-x":"Left", + "reference-y":"Bottom", + "x":112.0, + "y":0.0, + "width":56.0, + "height":210.0, + "thickness":0.0, + "rotation":0.0, + "fill":"0x8099ffff", + "stroke":"0xffffffff", + "shape":"Rectangle", + "lock":"false" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/controlsfx.properties b/src/controlsfx.properties new file mode 100644 index 0000000000000000000000000000000000000000..1e7b0c289a4f053149f4b4a4d96be8d0042e3320 --- /dev/null +++ b/src/controlsfx.properties @@ -0,0 +1,78 @@ +### Dialogs ### + +dlg.ok.button = OK +dlg.cancel.button = Cancel +dlg.yes.button = Yes +dlg.no.button = No +dlg.close.button = Close +dlg.detail.button.more = Show Details +dlg.detail.button.less = Hide Details + +### Common Dialogs ### + +font.dlg.title=Select font +font.dlg.header=Select font +font.dlg.sample.text=Sample +font.dlg.font.label=Font +font.dlg.style.label=Style +font.dlg.size.label=Size + +progress.dlg.title=Progress +progress.dlg.header=Progress + +login.dlg.title=Login +login.dlg.header=Enter user name and password +login.dlg.user.caption=User Name +login.dlg.pswd.caption=Password +login.dlg.login.button=Login + +exception.dlg.title = Exception Details +exception.dlg.header = Exception Details +exception.dlg.label = The exception stacktrace was: +exception.button.label = Open Exception + +### Wizard ### + +wizard.next.button = Next +wizard.previous.button = Previous + +### Property Sheet ### + +bean.property.change.error.title = Property Change Error +bean.property.change.error.header = Change is not allowed +bean.property.category.basic=Basic +bean.property.category.expert=Expert + +property.sheet.search.field.prompt = Search +property.sheet.group.mode.byname = By Name +property.sheet.group.mode.bycategory = By Category + +### Spreadsheet View ### + +spreadsheet.view.menu.axis = Show on axis +spreadsheet.view.menu.parentaxis = Show on parent axis +spreadsheet.view.menu.legend = Show on legend +spreadsheet.view.menu.node = Show on marks + +spreadsheet.view.menu.copy = Copy +spreadsheet.view.menu.paste = Paste +spreadsheet.view.menu.comment = Comment cell +spreadsheet.view.menu.comment.top-left = top left +spreadsheet.view.menu.comment.top-right = top right +spreadsheet.view.menu.comment.bottom-right = bottom right +spreadsheet.view.menu.comment.bottom-left = bottom left +spreadsheet.column.menu.fix = Fix column +spreadsheet.column.menu.unfix = Unfix column +spreadsheet.verticalheader.menu.fix = Fix row +spreadsheet.verticalheader.menu.unfix = Unfix row + +### Status Bar ### +statusbar.ok = OK + +### List Selection View ### +listSelectionView.header.source = Available +listSelectionView.header.target = Selected + +### PopOver ### +popOver.default.content = <No Content> +popOver.default.title = Info \ No newline at end of file diff --git a/src/fr/inria/structgraphics/.Rhistory b/src/fr/inria/structgraphics/.Rhistory new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/fr/inria/structgraphics/VisSketcher.java b/src/fr/inria/structgraphics/VisSketcher.java new file mode 100644 index 0000000000000000000000000000000000000000..c52e328bc5a838681abeeaf016d2b6c0cd23ad2b --- /dev/null +++ b/src/fr/inria/structgraphics/VisSketcher.java @@ -0,0 +1,117 @@ +package fr.inria.structgraphics; + +import fr.inria.structgraphics.graphics.MarkFactory; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.ui.DisplayPreferences; +import fr.inria.structgraphics.ui.DragManager; +import fr.inria.structgraphics.ui.inspector.InspectorView; +import fr.inria.structgraphics.ui.inspector.util.TooltipCreator; +import fr.inria.structgraphics.ui.spreadsheet.DataView; +import fr.inria.structgraphics.ui.viscanvas.Canvas; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.application.Application; +import javafx.beans.binding.Bindings; + +import javafx.stage.Stage; +import javafx.scene.Scene; +import javafx.scene.SceneAntialiasing; +import javafx.scene.control.ScrollPane; + +public class VisSketcher extends Application { + + @Override + public void start(Stage stage) { + double width = 800, height = 780; + + VisFrame frame = MarkFactory.createVisFrame(DisplayPreferences.CANVAS_WIDTH, DisplayPreferences.CANVAS_HEIGHT); + frame.initProperties(); + + try { + CanvasFrame canvasFrame = new CanvasFrame(stage); + Canvas canvas = canvasFrame.getCanvas(); + + ScrollPane scroller = new ScrollPane(); + scroller.setContent(canvas); + canvasFrame.setSketchArea(scroller); + + canvas.minWidthProperty().bind(Bindings.createDoubleBinding(() -> + scroller.getViewportBounds().getWidth(), scroller.viewportBoundsProperty())); + + canvasFrame.setVisFrame(frame); + //canvas.setContent(frame.getGroup()); + + + // TODO: Fix scrolling + //canvasFrame.setCenter(scroller); + + Scene scene = new Scene(canvasFrame, width, height, false, SceneAntialiasing.BALANCED); + scene.getStylesheets().addAll(getClass().getResource("application.css").toExternalForm(), CanvasFrame.STYLESHEETS); + + stage.setTitle("StructGraphics: Visualization Sketcher"); + stage.setScene(scene); + stage.setX(100); + stage.show(); + + // This is the inspector window + InspectorView inspector = new InspectorView(); + DragManager.create(inspector); + + inspector.setVisualizationFrame(frame); + + scene = new Scene(inspector, 600, height); + scene.getStylesheets().addAll(InspectorView.STYLESHEETS); + Stage inspectStage = new Stage(); + inspectStage.setTitle("StructGraphics: Inspector"); + inspectStage.setScene(scene); + inspectStage.setX(stage.getX() + stage.getWidth() + 10); + inspectStage.setY(stage.getY()); + inspectStage.show(); + + inspector.update(); + + // The data view + DataView dataview = new DataView(inspector); + scene = new Scene(dataview, 600, height); + scene.getStylesheets().add(DataView.STYLESHEETS); + + Stage dataStage = new Stage(); + dataStage.setTitle("StructGraphics: Spreadsheet"); + dataStage.setScene(scene); + dataStage.setX(inspectStage.getX() + inspectStage.getWidth() + 10); + dataStage.setY(inspectStage.getY()); + dataStage.show(); + + /* + LibraryStage libraryStage = new LibraryStage(canvasFrame); + libraryStage.setX(stage.getX() - libraryStage.getWidth() - 5); + libraryStage.setY(stage.getY());*/ + + stage.setOnCloseRequest( event -> {canvasFrame.close(); inspectStage.close(); dataStage.close();}); + inspectStage.setOnCloseRequest( event -> {canvasFrame.close(); stage.close(); dataStage.close();}); + dataStage.setOnCloseRequest( event -> {canvasFrame.close(); stage.close(); inspectStage.close();}); + + // TODO: temporarry for study purposes + //stage.setOnCloseRequest(e->e.consume()); + //inspectStage.setOnCloseRequest(e->e.consume()); + //dataStage.setOnCloseRequest(e->e.consume()); + + + canvasFrame.setViews( inspector, dataview); + canvasFrame.setMenubars(stage, inspectStage); + + TooltipCreator.hackTooltipStartTiming(); + + + + + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + launch(args); + } + + +} diff --git a/src/fr/inria/structgraphics/application.css b/src/fr/inria/structgraphics/application.css new file mode 100644 index 0000000000000000000000000000000000000000..83d6f3343843c65d5dfaf3fedb97b6494c19113d --- /dev/null +++ b/src/fr/inria/structgraphics/application.css @@ -0,0 +1 @@ +/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */ \ No newline at end of file diff --git a/src/fr/inria/structgraphics/graphics/Arrow.java b/src/fr/inria/structgraphics/graphics/Arrow.java new file mode 100644 index 0000000000000000000000000000000000000000..a4509e5eebc4315f980771a3846646095a45b2bc --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/Arrow.java @@ -0,0 +1,156 @@ +package fr.inria.structgraphics.graphics; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.DoubleProperty; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Line; +import javafx.scene.shape.Shape; + +public class Arrow extends Group { + + private final Line line; + + public Arrow() { + this(new Line(), new Line(), new Line()); + } + + private static final double arrowLength = 6; + private static final double arrowWidth = 5; + + private Arrow(Line line, Line arrow1, Line arrow2) { + super(line, arrow1, arrow2); + this.line = line; + InvalidationListener updater = o -> { + double ex = getEndX(); + double ey = getEndY(); + double sx = getStartX(); + double sy = getStartY(); + + arrow1.setEndX(ex); + arrow1.setEndY(ey); + arrow2.setEndX(ex); + arrow2.setEndY(ey); + + if(Math.abs(ex - sx) < 8 && Math.abs(ey - sy) < 8) { + arrow1.setStartX(ex); + arrow1.setStartY(ey); + arrow2.setStartX(ex); + arrow2.setStartY(ey); + return; + } + + + if (ex == sx && ey == sy) { + // arrow parts of length 0 + arrow1.setStartX(ex); + arrow1.setStartY(ey); + arrow2.setStartX(ex); + arrow2.setStartY(ey); + } else { + double factor = arrowLength / Math.hypot(sx-ex, sy-ey); + double factorO = arrowWidth / Math.hypot(sx-ex, sy-ey); + + // part in direction of main line + double dx = (sx - ex) * factor; + double dy = (sy - ey) * factor; + + // part ortogonal to main line + double ox = (sx - ex) * factorO; + double oy = (sy - ey) * factorO; + + arrow1.setStartX(ex + dx - oy); + arrow1.setStartY(ey + dy + ox); + arrow2.setStartX(ex + dx + oy); + arrow2.setStartY(ey + dy - ox); + } + }; + + // add updater to properties + startXProperty().addListener(updater); + startYProperty().addListener(updater); + endXProperty().addListener(updater); + endYProperty().addListener(updater); + updater.invalidated(null); + } + + + public Arrow getCopie(double dx, double dy) { + Arrow arrow = new Arrow(); + arrow.setEnds(line.getStartX() + dx, line.getEndX() + dx, line.getStartY() + dy, line.getEndY() + dy); + + return arrow; + } + + // start/end properties + + public final void setEnds(double x1, double x2, double y1, double y2) { + line.setStartX(x1); + line.setStartY(y1); + + line.setEndX(x2); + line.setEndY(y2); + } + + + public final void setStartX(double value) { + line.setStartX(value); + } + + public final double getStartX() { + return line.getStartX(); + } + + public final DoubleProperty startXProperty() { + return line.startXProperty(); + } + + public final void setStartY(double value) { + line.setStartY(value); + } + + public final double getStartY() { + return line.getStartY(); + } + + public final DoubleProperty startYProperty() { + return line.startYProperty(); + } + + public final void setEndX(double value) { + line.setEndX(value); + } + + public final double getEndX() { + return line.getEndX(); + } + + public final DoubleProperty endXProperty() { + return line.endXProperty(); + } + + public final void setEndY(double value) { + line.setEndY(value); + } + + public final double getEndY() { + return line.getEndY(); + } + + public final DoubleProperty endYProperty() { + return line.endYProperty(); + } + + public void setStroke(Paint paint) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setStroke(paint); + } + } + + public void setStrokeWidth(double border) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setStrokeWidth(border); + } + } +} \ No newline at end of file diff --git a/src/fr/inria/structgraphics/graphics/ComplexShape.java b/src/fr/inria/structgraphics/graphics/ComplexShape.java new file mode 100644 index 0000000000000000000000000000000000000000..5be7868ff7d921e96570346b16e23ab852370ae2 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/ComplexShape.java @@ -0,0 +1,106 @@ +package fr.inria.structgraphics.graphics; + +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; + +public class ComplexShape extends Group { + + // Create a ghost for highlighting the shape + //TODO: ... + protected Group ghost; + private boolean highlighted = false; + + public ComplexShape() { + super(); + + ghost = new Group(); + getChildren().add(ghost); + ghost.setVisible(false); + } + + public void setFill(Paint paint) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setFill(paint); + } + } + + public void setStroke(Paint paint) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setStroke(paint); + } + } + + public void setStrokeWidth(double border) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setStrokeWidth(border); + } + } + + public void clear() { // TODO + getChildren().remove(1, getChildren().size()); + ghost.getChildren().clear(); + } + + public void add(Shape shape) { + getChildren().add(0, shape); + shape.setSmooth(true); + //if(highlighted) addToGhost(shape); + } + + public void remove(Shape shape) { + getChildren().remove(shape); + } + + public void replaceBy(Shape shape) { + getChildren().remove(0); + add(shape); + } + + public boolean intersects(Shape trace) { + if(trace == null) return false; + for(Node shape: getChildren()) { + if(shape instanceof Shape) { + Shape intersection = Shape.intersect((Shape) shape, trace); + if(!(intersection instanceof Path) || !((Path)intersection).getElements().isEmpty()) + return true; + } + } + + return false; + } + + + public void addToGhost(Group group) { + for(Node node: group.getChildren()) { + Shape shape = (Shape)node; + shape.setStroke( Color.CORNFLOWERBLUE); + shape.setFill(null); + shape.setStrokeWidth(shape.getStrokeWidth() + 1.5); + } + + ghost.getChildren().addAll(group.getChildren()); + } + + public void addToGhost(Shape shape) { + ghost.getChildren().add(shape); + shape.setStroke( Color.CORNFLOWERBLUE); + shape.setFill(null); + shape.setStrokeWidth(shape.getStrokeWidth() + 1.5); + } + + public void setHighlight(boolean highlight) { + if(highlight && !highlighted) { + highlighted = true; + ghost.setVisible(true); + } else if(!highlight && highlighted) { + highlighted = false; + ghost.setVisible(false); + } + } + + +} diff --git a/src/fr/inria/structgraphics/graphics/ConnectedCollection.java b/src/fr/inria/structgraphics/graphics/ConnectedCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..799f5238dd6633c42817a5e41cb8f4de1d0e9316 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/ConnectedCollection.java @@ -0,0 +1,200 @@ +package fr.inria.structgraphics.graphics; + +import java.util.Map; +import java.util.TreeMap; + +import com.google.common.io.LineProcessor; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.tools.SelectFilter; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.ReferenceStructureCreator; +import fr.inria.structgraphics.ui.viscanvas.groupings.XReferenceStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.YReferenceStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.Grouping.Type; +import javafx.beans.Observable; +import javafx.beans.property.Property; +import javafx.collections.ObservableList; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Shape; + +public abstract class ConnectedCollection extends VisCollection { + protected Shape primitif; + protected Shape ghost; + + protected boolean curveSelected = false; + + public ConnectedCollection(Container parent, boolean toend) { + super(parent, toend); + } + + + public ConnectedCollection(Container parent, boolean toend, ObservableList<Mark> marks) { + super(parent, toend); + + ReferenceStructureCreator creator = new ReferenceStructureCreator(marks, Type.Collection); + XReferenceStructure xstruct = creator.getXReferenceStructure(); + YReferenceStructure ystruct = creator.getYReferenceStructure(); + + setLevel(marks.get(0).getLevel() + 1); // TODO: To remove??? + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + setRefCoords(refCoords); + + getConstraintXProperty().set(xstruct.constraint); + getConstraintYProperty().set(ystruct.constraint); + + PositionCoords coords = new PositionCoords(this, xstruct.getRefValue(), ystruct.getRefValue()); + setCoords(coords); + + for(Mark mark: marks) { + mark.getCoords().xRef.set(xstruct.getRef()); + mark.getCoords().yRef.set(ystruct.getRef()); + attach(mark, xstruct.isShared(), ystruct.isShared()); + } + + refresh(); + + CollectionPropertyStructure propertyStructure = new CollectionPropertyStructure(marks); + setPropertyStructure(propertyStructure); + + initProperties(); + + // initializeShape(); + } + + public boolean isCurveSelected() { + return curveSelected; + } + + public boolean isSingleSibling() { + if(!(container instanceof VisCollection) || + ((VisCollection)container).alignXProperty.get() == YSticky.No && + ((VisCollection)container).alignYProperty.get() == XSticky.No) + return true; + else return false; + } + + + @Override + public void initProperties() { + border.set(1.5); + addProperty(border); + addProperty(strokePaint); + + super.initProperties(); + } + + + @Override + protected void updateBasicShape() { + labels.update(); + refreshShape(); + updateGhost(); + } + + protected abstract Shape createShape(); + protected abstract void refreshShape(); + + + /* + @Override + public boolean monoselect(MarkSelection selectedMarks, Circle trace) { // To review how to prioritize its selection or not... + + curveSelected = false; + + boolean consumed = super.monoselect(selectedMarks, trace); + if(consumed) { + return consumed; + } + + if(shape.intersects(trace) && components.size() > 0) { + pinX = components.get(0).coords.getX(); + pinY = components.get(0).coords.getY(); + + selectedMarks.add(this); + + curveSelected = true; + + return true; + } + + return false; + }*/ + + @Override + public void pin() { + if(!curveSelected) super.pin(); + } + + + @Override + public void select(MarkSelection selectedMarks, Shape trace, SelectFilter filter) { + curveSelected = false; + super.select(selectedMarks, trace, filter); + } + + + @Override + public boolean controlselect(MarkSelection selectedMarks, Circle trace) { + curveSelected = false; + return super.controlselect(selectedMarks, trace); + } + + + @Override + public void setHighlight(boolean highlight, boolean enableControllers) { + super.setHighlight(highlight, enableControllers); + shape.setHighlight(highlight); + } + + + @Override + public void translate(Line lineTrace) { + if(!curveSelected) { + super.translate(lineTrace); + } + else if(components.size() > 0){ + double x = pinX + lineTrace.getEndX() - lineTrace.getStartX() - components.get(0).coords.getX(); + double y = pinY - lineTrace.getEndY() + lineTrace.getStartY() - components.get(0).coords.getY(); + + if(childPropertyStructure.isXShared()) { + Mark mark = components.get(0); + mark.coords.x.set(x + mark.coords.getX()); + } else for(Mark mark: components) { + mark.coords.x.set(x + mark.coords.getX()); + } + + if(childPropertyStructure.isYShared()) { + Mark mark = components.get(0); + mark.coords.y.set(y + mark.coords.getY()); + } else for(Mark mark: components) { + mark.coords.y.set(y + mark.coords.getY()); + } + + updateInteractor(); + //updateBasicShape(); + } + } + + @Override + public void invalidated(Observable observable) { + super.invalidated(observable); + updateBasicShape(); + } + + @Override + public Map<PropertyName, Property> getSelfProperties() { + Map<PropertyName, Property> map = new TreeMap<>(); + for(Property property: properties) map.put(new PropertyName(property.getName()), property); + return map; + } + + protected abstract Shape primitifCopy(double dx, double dy, boolean shadow); +} diff --git a/src/fr/inria/structgraphics/graphics/Container.java b/src/fr/inria/structgraphics/graphics/Container.java new file mode 100644 index 0000000000000000000000000000000000000000..5f006af77ca2b82743f85e2c44a9eeb02ab2bc4e --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/Container.java @@ -0,0 +1,297 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import com.sun.prism.image.Coords; + +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.IdentifierProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.tools.SelectFilter; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import javafx.beans.property.Property; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.control.TextField; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Shape; + +public abstract class Container { + + protected List<Mark> components = new ArrayList<Mark>(); + protected Group group = new Group(); + protected List<Property> properties = new ArrayList<Property>(); + protected Map<PropertyName, FlexibleListProperty> tabularProperties = null; // TODO: Not currently used. To remove? + + protected ReferenceCoords refCoords; + + public StringProperty id; + + protected final ChangeListener changeListener = new ChangeListener() { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) { + update(); + } + }; + + public Container() { + createIdProperty(); + } + + protected void createIdProperty() { + id = new IdentifierProperty(this, "id"); + } + + public void refreshID() { + for(Mark mark:components) + mark.refreshID(); + } + + // TODO: To change.... + public void addProperty(Property property) { + property.addListener(changeListener); + properties.add(property); + } + + public void removeProperty(Property property) { // TODO: I will probably need to also remove it (if needed from ) + property.removeListener(changeListener); + properties.remove(property); + } + + public void addPropertiesColumn(FlexibleListProperty property) { + if(tabularProperties == null) tabularProperties = new TreeMap<>(); + + tabularProperties.put(new PropertyName(property.getName()), property); // TODO: To add change listeners!!! + + property.addListener(changeListener); + for(Property p:property) p.addListener(changeListener); + } + + + public boolean hasTabularProperties() { + return tabularProperties != null; + } + + /* + * Check is the mark should be added to the end of the childrent or not. By default it is true + */ + public boolean addToEnd(double x, double y) { + return true; + } + + public void setRefCoords(ReferenceCoords coords) { + this.refCoords = coords; + } + + public ReferenceCoords getRefCoords() { + return refCoords; + } + + public VisBody getRootVirtualGroup() { + return null; + } + + public VisBody getVirtualGroup() { + return null; + } + + public int leafs() { + if(components == null || components.size() == 0) return 1; + + int counter = 0; + for(Mark mark:components) { + counter += mark.leafs(); + } + + return counter; + } + + public void update() { + updateProperties(); + updateAll(); + } + + public void initProperties() { + if(refCoords != null) { + addProperty(refCoords.getContainerXRefProperty()); + addProperty(refCoords.getContainerYRefProperty()); + } + } + + public void updateProperties() { + // empty + } + + public void addMark(Mark mark, boolean toend){ + mark.container = this; + + if(toend) { + components.add(mark); + group.getChildren().add(mark.group); + } + else { + components.add(0, mark); + group.getChildren().add(0, mark.group); + } + + refreshID(); + } + + public void removeMark(Mark mark) { + components.remove(mark); + refreshID(); + } + + public Group getGroup(){ + return group; + } + + protected void updateAll() { + clear(); + updateShape(); + + for(Container component: components) + component.updateAll(); + } + + public void clear() { + group.getTransforms().clear(); + } + + public List<Property> getProperties(){ + return properties; + } + + public Map<PropertyName, Property> getSelfProperties() { + Map<PropertyName, Property> map = new TreeMap<>(); + for(Property property: properties) map.put(new PropertyName(property.getName()), property); + return map; + } + + public Map<PropertyName, FlexibleListProperty> getTabularProperties(){ + return tabularProperties; + } + + public void addLeafIDs(List<Property> ids){ + for(Mark mark:components) + mark.addLeafIDs(ids); + } + + public void addIDs(List<Property> ids, int level) { + for(Mark mark:components) + mark.addIDs(ids, level); + } + + public List<Mark> getComponents(){ + return components; + } + + public Mark getComponentAt(int i){ + return components.get(i); + } + + public Container getComponent(String sid){ + for(Mark component: components) { + Container mark = component.getComponent(sid); + if(mark != null) return mark; + } + + return null; + } + + public abstract void updateShape(); + // protected abstract Point2D getReferencePoint(double cx, double cy, RefX containerXRef, RefY containerYRef); + public abstract Point2D getReferencePoint(double cx, double cy); + public abstract double getRefAngle(double x); + + public Point2D getOffsetPoint() { + return getReferencePoint(0, 0); + } + + // protected abstract double getRefAngle(double x, RefX containerXRef); + + public abstract String getName(); + public abstract String getType(); + + + public void select(MarkSelection selectedMarks, Shape trace, SelectFilter filter) { + for(Mark mark: components) + mark.select(selectedMarks, trace, filter); + } + + public boolean monoselect(MarkSelection selectedMarks, Circle trace) { + for(int i = components.size() - 1; i >=0; --i) { + if(((Mark)components.get(i)).controlselect(selectedMarks, trace)) return true; + } + + for(int i = components.size() - 1; i >=0; --i) { + if(((Mark)components.get(i)).monoselect(selectedMarks, trace)) return true; + } + + return false; + } + + + public ShapeMark getShape(double x, double y) { + for(Mark mark: components) { + double x_ = x - mark.coords.getX(); + double y_ = y - mark.coords.getY(); + + ShapeMark mark_ = mark.getShape(x_, y_); + if(mark_ != null) return mark_; + } + + return null; + } + + + public double getRootWidth() { + return 0; + } + + public double getRootHeight() { + return 0; + } + + + public void addTextField(TextField textField, boolean add) { + if(add) group.getChildren().add(textField); + else group.getChildren().remove(textField); + } + + public void showGroupInteractors(boolean show) { + for(Mark mark:components) + mark.showGroupInteractors(show); + } + + + public void bringToFront(Mark mark) {} + + public void sendToBack(Mark mark) {} + + public void updateReordering() {} + + + public boolean hasConnections() { + return false; + } + + + public double getYIn(Container container) { + return 0; + } + + public double getXIn(Container container) { + return 0; + } +} + diff --git a/src/fr/inria/structgraphics/graphics/Fill.java b/src/fr/inria/structgraphics/graphics/Fill.java new file mode 100644 index 0000000000000000000000000000000000000000..3a9afce4b20459e93c51c2b699ed40d33fe25d44 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/Fill.java @@ -0,0 +1,27 @@ +package fr.inria.structgraphics.graphics; + +import java.util.Map; +import java.util.TreeMap; + +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +public class Fill { + + private final static Fill singleton = new Fill(); + + private Map<String, Paint> mapping; + + private Fill() { + mapping = new TreeMap<String, Paint>(); + mapping.put("empty", Color.WHITE); + mapping.put("pattern1", Color.LIGHTGRAY); + mapping.put("pattern2", Color.GRAY); + mapping.put("pattern3", Color.DARKGRAY); + } + + + public static Paint getPaint(String name) { + return singleton.mapping.get(name); + } +} diff --git a/src/fr/inria/structgraphics/graphics/FlowCreator.java b/src/fr/inria/structgraphics/graphics/FlowCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..50a77dd6efd166a6401eac17a4588bbaa9080062 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/FlowCreator.java @@ -0,0 +1,140 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.splines.CubicSpline; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import javafx.geometry.Point2D; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; + +public class FlowCreator { + private static final double defaultArrowHeadSize = 8.0; + + public static void createCurve(Path path, FlowConnection flowConnection) { + double dx; + + if(flowConnection.startX() < flowConnection.endX()) dx = Math.min(50, (flowConnection.endX() - flowConnection.startX())/3); + else dx = Math.max(-50, (flowConnection.endX() - flowConnection.startX())/3); + + MoveTo moveTo = new MoveTo(); + moveTo.setX(flowConnection.startX()); + moveTo.setY(flowConnection.startY()); + + CubicCurveTo cubicTo = new CubicCurveTo(); + cubicTo.setControlX1(flowConnection.startX() + dx); + cubicTo.setControlY1(flowConnection.startY()); + cubicTo.setControlX2(flowConnection.endX() - dx); + cubicTo.setControlY2(flowConnection.endY()); + cubicTo.setX(flowConnection.endX()); + cubicTo.setY(flowConnection.endY()); + + path.getElements().add(moveTo); + path.getElements().add(cubicTo); + } + + public static void createFlowConnectionPath(FlowPath path, FlowConnection flowConnection) { + double dx; + + if(flowConnection.startX() < flowConnection.endX()) dx = Math.min(50, (flowConnection.endX() - flowConnection.startX())/3); + else dx = Math.max(-50, (flowConnection.endX() - flowConnection.startX())/3); + + double bottom1 = flowConnection.startYBottom(); + double bottom2 = flowConnection.endYBottom(); + + double y1 = bottom1 + flowConnection.getOrigin().getOriginFlag(); + double y2 = y1 + flowConnection.weightProperty().get(); + + double y4 = bottom2 + flowConnection.getDestination().getDestinationFlag(); + double y3 = y4 + flowConnection.weightProperty().get(); + + double x1 = flowConnection.startX(); + double x2 = flowConnection.endX(); + + path.setY1(y1, y2); + path.setY2(y4, y3); + path.setX(x1, x2); + + MoveTo moveTo = new MoveTo(); + moveTo.setX(x1); + moveTo.setY(y1); + + LineTo lineTo = new LineTo(); + lineTo.setX(x1); + lineTo.setY(y2); + + CubicCurveTo cubicTo = new CubicCurveTo(); + cubicTo.setControlX1(x1 + dx); + cubicTo.setControlY1(y2); + cubicTo.setControlX2(x2 - dx); + cubicTo.setControlY2(y3); + cubicTo.setX(x2); + cubicTo.setY(y3); + + LineTo lineTo2 = new LineTo(); + lineTo2.setX(x2); + lineTo2.setY(y4); + + CubicCurveTo cubicTo2 = new CubicCurveTo(); + cubicTo2.setControlX1(x2 - dx); + cubicTo2.setControlY1(y4); + cubicTo2.setControlX2(x1 + dx); + cubicTo2.setControlY2(y1); + cubicTo2.setX(x1); + cubicTo2.setY(y1); + + path.getElements().add(moveTo); + path.getElements().add(lineTo); + path.getElements().add(cubicTo); + path.getElements().add(lineTo2); + path.getElements().add(cubicTo2); + + flowConnection.getOrigin().increaseOriginFlag(flowConnection.weightProperty().get()); + flowConnection.getDestination().increaseDestinationFlag(flowConnection.weightProperty().get()); + } + + public static void createLine(Path path, FlowConnection flowConnection) { + MoveTo moveTo = new MoveTo(); + moveTo.setX(flowConnection.startX()); + moveTo.setY(flowConnection.startY()); + path.getElements().add(moveTo); + + LineTo lineTo = new LineTo(); + lineTo.setX(flowConnection.endX()); + lineTo.setY(flowConnection.endY()); + path.getElements().add(lineTo); + } + + public static void createArrow(Path path, FlowConnection flowConnection) { + double startX = flowConnection.startX(); + double startY = flowConnection.startY(); + double endX = flowConnection.endX(); + double endY = flowConnection.endY(); + + //Line + path.getElements().add(new MoveTo(startX, startY)); + path.getElements().add(new LineTo(endX, endY)); + + //ArrowHead + double angle = Math.atan2((endY - startY), (endX - startX)) - Math.PI / 2.0; + double sin = Math.sin(angle); + double cos = Math.cos(angle); + //point1 + double x1 = (- 1.0 / 2.0 * cos + Math.sqrt(3) / 2 * sin) * defaultArrowHeadSize + endX; + double y1 = (- 1.0 / 2.0 * sin - Math.sqrt(3) / 2 * cos) * defaultArrowHeadSize + endY; + //point2 + double x2 = (1.0 / 2.0 * cos + Math.sqrt(3) / 2 * sin) * defaultArrowHeadSize + endX; + double y2 = (1.0 / 2.0 * sin - Math.sqrt(3) / 2 * cos) * defaultArrowHeadSize + endY; + + path.getElements().add(new LineTo(x1, y1)); + path.getElements().add(new LineTo(x2, y2)); + path.getElements().add(new LineTo(endX, endY)); + + path.setStrokeWidth(flowConnection.weightProperty().get()); + } + + +} diff --git a/src/fr/inria/structgraphics/graphics/FlowPath.java b/src/fr/inria/structgraphics/graphics/FlowPath.java new file mode 100644 index 0000000000000000000000000000000000000000..3e581465fd8720caebd5a7b6d2a9de4531382bc0 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/FlowPath.java @@ -0,0 +1,34 @@ +package fr.inria.structgraphics.graphics; + +import javafx.scene.shape.Path; + +public class FlowPath extends Path { + private double y1bottom, y1top, y2bottom, y2top; + private double left, right; + + public void setY1(double bottom, double top) { + y1bottom = bottom; + y1top = top; + } + + public void setY2(double bottom, double top) { + y2bottom = bottom; + y2top = top; + } + + public void setX(double left, double right) { + this.left = left; + this.right = right; + } + + public double centerY() { + //return (y1bottom + y2top)/2; + + return (2*y1bottom + y2top)/3; + } + + public double centerX() { + //return (left + right)/2; + return (2*left + right)/3; + } +} diff --git a/src/fr/inria/structgraphics/graphics/GenericCurve.java b/src/fr/inria/structgraphics/graphics/GenericCurve.java new file mode 100644 index 0000000000000000000000000000000000000000..0bc0068a9e3b9c43a6630e53ffa2176da1d49e29 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/GenericCurve.java @@ -0,0 +1,142 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.graphics.splines.FlattenedPath; +import fr.inria.structgraphics.types.DoubleListProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.geometry.Point2D; +import javafx.scene.paint.Color; +import javafx.scene.shape.Shape; +import javafx.scene.transform.Rotate; + +public abstract class GenericCurve extends Mark { + + protected double width = 0, height = 0; + protected double length = 0; + protected Point2D center; // central point with respect to the first point of the curve + + protected FlexibleListProperty original_xs = new DoubleListProperty(this, "x"); + protected FlexibleListProperty original_ys = new DoubleListProperty(this, "y"); + + protected ArrayList<Point2D> points; + protected Shape path; + protected FlattenedPath flattenedPath; + + protected Point2D middle; + + public GenericCurve(Container parent) { + super(parent); + } + + public GenericCurve(Container parent, ArrayList<Point2D> points){ + super(parent); + + for(Point2D p: points) { + original_xs.get().add(new SimpleDoubleProperty(p.getX())); + original_ys.get().add(new SimpleDoubleProperty(p.getY())); + } + } + + @Override + public void initProperties() { + super.initProperties(); + + addProperty(angle); + addProperty(border); + addProperty(strokePaint); + + addPropertiesColumn(original_xs); + addPropertiesColumn(original_ys); + } + + public void addPoint(double x, double y) { + original_xs.get().add(new SimpleDoubleProperty(x)); + original_ys.get().add(new SimpleDoubleProperty(y)); + } + + public Point2D getStart() { + return points.get(0); + } + + public Point2D getEnd() { + return points.get(points.size() - 1); + } + + public Point2D getMiddle() { + return middle; + } + + public double width() { + return width; + } + + public double height() { + return height; + } + + @Override + public void updateShape() { // TODO: This probably needs some refreshing... + points = new ArrayList<Point2D>(); + /////////////////// + for(int i = 0; i < original_xs.get().size(); ++i) { + double x = (double)original_xs.get().get(i).getValue(); + double y = (double)original_ys.get().get(i).getValue(); + + double cx = x + coords.getX(), cy = y + coords.getY(); + points.add(container.getReferencePoint(cx, cy)); + } + + updateBasicShape(); + calculateLength(); + middle = getPoint(length/2, 0); + + // TODO: check this angle rotation (with respect to the center???) + if(angle.get() != 0) group.getTransforms().add(new Rotate(angle.get(), center.getX(), center.getY())); + +// if(paint != null) shape.setFill(paint.get()); +// else shape.setFill(null); + + if(strokePaint != null) shape.setStroke(strokePaint.get()); + else shape.setStroke(Color.DARKGRAY); + + shape.setStrokeWidth(border.get()); + + } + + protected abstract void calculateLength(); + + protected double transformX(double cx, RefX containerXRef) { + // X coordinate with respect to the left end (start point) of the curve + if(containerXRef == RefX.Left) return cx; + else if(containerXRef == RefX.Right) return length + cx; + else return length/2 + cx; + } + + /* + * It transforms to global coordinates + */ + @Override + public Point2D getReferencePoint(double cx, double cy) { + Point2D p = getPoint(transformX(cx, refCoords.containerXRef.get()), cy); +// Point2D center = getCenter(); + return new Point2D(p.getX() /*- center.getX()*/, p.getY() /*- center.getY()*/); + } + + @Override + public Point2D getOffsetPoint() { + return new Point2D(transformX(0, refCoords.containerXRef.get()), 0); + } + + protected Point2D getCenter() { + return center; + } + + /* Point at l, y with respect to the first point of the curve + * + */ + protected abstract Point2D getPoint(double l, double y); +} diff --git a/src/fr/inria/structgraphics/graphics/GenericShapeMark.java b/src/fr/inria/structgraphics/graphics/GenericShapeMark.java new file mode 100644 index 0000000000000000000000000000000000000000..8fb7bea629f8ed7194720cacc23e5e63c7c22663 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/GenericShapeMark.java @@ -0,0 +1,143 @@ +package fr.inria.structgraphics.graphics; + +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.ShapeProperty.Type; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.shape.Ellipse; +import javafx.scene.shape.Line; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; + +public class GenericShapeMark extends ShapeMark { + + protected GenericShapeMark(Container parent, boolean toend, ShapeProperty.Type type, double w, double h){ + super(parent, toend, type, w, h); + + } + + @Override + public void initProperties() { + super.initProperties(); + + properties.add(shapeProperty); + shapeProperty.addListener(new ChangeListener<ShapeProperty.Type>() { + @Override + public void changed(ObservableValue<? extends Type> observable, Type oldValue, Type newValue) { + changeShape(newValue); + } + }); + } + + private void changeShape(Type newShape) { + primitif = createShape(); + shape.replaceBy(primitif); + update(); + } + + @Override + protected Shape createShape() { + switch(shapeProperty.get()) { + case Ellipse: + return new Ellipse(0, 0, Math.abs(width.get()/2), Math.abs(height.get()/2)); + case Rectangle: + return new Rectangle(-Math.abs(width.get())/2, -Math.abs(height.get())/2, Math.abs(width.get()), Math.abs(height.get())); + case Triangle: + Path path = new Path(); + createTriangle(path, width.get(), height.get()); + return path; + case Line: + return new Line(-width.get()/2, -height.get()/2, width.get()/2, height.get()/2); + + default: return null; + } + } + + @Override + protected void refreshShape() { + switch(shapeProperty.get()) { + case Ellipse: + Ellipse ellipse = (Ellipse)primitif; + ellipse.radiusXProperty().set(Math.abs(width.get()/2)); + ellipse.radiusYProperty().set(Math.abs(height.get()/2)); + break; + + case Rectangle: + Rectangle rect = (Rectangle)primitif; + rect.xProperty().set(-Math.abs(width.get())/2); + rect.yProperty().set(-Math.abs(height.get())/2); + rect.widthProperty().set(Math.abs(width.get())); + rect.heightProperty().set(Math.abs(height.get())); + break; + + case Triangle: + Path path = (Path)primitif; + path.getElements().clear(); + createTriangle(path, width.get(), height.get()); + break; + + case Line: + + Line line = (Line)primitif; + line.startXProperty().set(-width.get()/2); + line.startYProperty().set(-height.get()/2); + line.endXProperty().set(width.get()/2); + line.endYProperty().set(height.get()/2); + + default: + } + } + + + private void createTriangle(Path path, double w, double h) { + MoveTo moveTo = new MoveTo(); + moveTo.setX(0.0); + moveTo.setY(-h/2); + path.getElements().add(moveTo); + + LineTo lineTo = new LineTo(); + lineTo.setX(Math.abs(w)/2); + lineTo.setY(h/2); + path.getElements().add(lineTo); + + lineTo = new LineTo(); + lineTo.setX(-Math.abs(w)/2); + lineTo.setY(h/2); + path.getElements().add(lineTo); + + lineTo = new LineTo(); + lineTo.setX(Math.abs(0)); + lineTo.setY(-h/2); + path.getElements().add(lineTo); + } + + @Override + public String getName() { + return shapeProperty.get().name(); + } + + @Override + public String getType() { + return shapeProperty.get().name(); } + + + @Override + public Shadow getShadow(double dx, double dy, double delta) { + dx *= delta; + dy *= delta; + + switch(shapeProperty.get()) { + case Ellipse: + return new Shadow(this, dx, dy, new Ellipse(dx, dy, Math.abs(width.get()/2), Math.abs(height.get()/2))); + case Line: + return new Shadow(this, dx, dy, new Line(dx-width.get()/2, dy-height.get()/2, dx + width.get()/2, dy + height.get()/2)); + default: + return new Shadow(this, dx, dy, new Rectangle(dx-Math.abs(width.get())/2, dy-Math.abs(height.get())/2, + Math.abs(width.get()), Math.abs(height.get()))); // TODO: ??? + } + } + +} diff --git a/src/fr/inria/structgraphics/graphics/HierarchyPos.java b/src/fr/inria/structgraphics/graphics/HierarchyPos.java new file mode 100644 index 0000000000000000000000000000000000000000..ac37da8eda4caf26467eb9cbecf9d8325400e919 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/HierarchyPos.java @@ -0,0 +1,33 @@ +package fr.inria.structgraphics.graphics; + +import java.util.TreeMap; + +public class HierarchyPos extends TreeMap<Integer, Integer>{ + + private Mark mark; + + public HierarchyPos(Mark mark) { + super(); + this.mark = mark; + + if(mark.getContainer() instanceof VisBody) { + put(mark.level, mark.getContainer().getComponents().indexOf(mark)); + goUp((VisBody)mark.getContainer()); + } + } + + public void goUp(VisBody mark) { + if(mark.getContainer() instanceof VisBody) { + put(mark.level, mark.getContainer().getComponents().indexOf(mark)); + goUp((VisBody)mark.getContainer()); + } + } + + public int getBottomLevel() { + return mark.level; + } + + public Integer getIndexAt(int level) { + return get(level); + } +} diff --git a/src/fr/inria/structgraphics/graphics/LineConnectedCollection.java b/src/fr/inria/structgraphics/graphics/LineConnectedCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..8e93e09e27c59983f3fa118a53465e306295c0e6 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/LineConnectedCollection.java @@ -0,0 +1,911 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.graphics.splines.CubicSpline; +import fr.inria.structgraphics.types.ColoringSchemeProperty; +import fr.inria.structgraphics.types.FillColorProperty; +import fr.inria.structgraphics.types.LineTypeProperty; +import fr.inria.structgraphics.types.OpacityProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.ColoringSchemeProperty.Scheme; +import fr.inria.structgraphics.types.LineTypeProperty.Type; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnections; +import fr.inria.structgraphics.ui.viscanvas.groupings.PropertyStructure; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.geometry.Point2D; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.ClosePath; +import javafx.scene.shape.Line; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; + +public class LineConnectedCollection extends ConnectedCollection { + + private FlowConnections flowConnections; + public LineTypeProperty lineProperty; + + protected Map<FlowConnection, FlowPath> connectionShapes = new Hashtable<>(); + protected Map<FlowConnection, Text> connectionLabels = new Hashtable<>(); + protected FlowConnection connectionSelected = null; + protected DataVariable activeVariable; + +// public StrokeWidthProperty = new StrokeWidthProperty(this, 0); + public FillColorProperty flowPaint = new FillColorProperty (this, Color.rgb(140, 140, 158)); + public OpacityProperty flowOpacity = new OpacityProperty(this, 0.5); + public ColoringSchemeProperty coloringScheme = new ColoringSchemeProperty(this, Scheme.Common); + + public LineConnectedCollection(Container parent, boolean toend) { + super(parent, toend); + } + + public LineConnectedCollection(Container parent, boolean toend, ObservableList<Mark> marks) { + super(parent, toend, marks); + + } + + @Override + public void initProperties() { + super.initProperties(); + + //if(flowPaint != null) flowPaint.set(Color.rgb(170, 170, 188)); + + if(lineProperty == null) { + lineProperty = new LineTypeProperty(this, LineTypeProperty.Type.None); + border.getActiveProperty().set(false); + strokePaint.getActiveProperty().set(false); + } + properties.add(lineProperty); + + if(lineProperty.get() == LineTypeProperty.Type.TopBottom) { + bringConnectionsFront(); + } + + lineProperty.addListener(new ChangeListener<LineTypeProperty.Type>() { + @Override + public void changed(ObservableValue<? extends LineTypeProperty.Type> observable, LineTypeProperty.Type oldValue, LineTypeProperty.Type newValue) { + if(newValue != LineTypeProperty.Type.None) reorderChildren(true); + changeShape(newValue); + + if(newValue == LineTypeProperty.Type.None) { + border.getActiveProperty().set(false); + strokePaint.getActiveProperty().set(false); + sendConnectionsBack(); + } else if(newValue == LineTypeProperty.Type.Straight || newValue == LineTypeProperty.Type.Bezier + || newValue == LineTypeProperty.Type.TopBottom) { + border.getActiveProperty().set(true); + strokePaint.getActiveProperty().set(true); + + if(newValue == LineTypeProperty.Type.TopBottom) { + bringConnectionsFront(); + } else { + sendConnectionsBack(); + } + + } else { + border.getActiveProperty().set(false); + strokePaint.getActiveProperty().set(true); + //strokePaint.set(Color.color(.85, .8, .8, .5)); + sendConnectionsBack(); + } + } + }); + + if(lineProperty.get() == LineTypeProperty.Type.StraightSolid || lineProperty.get() == LineTypeProperty.Type.BezierSolid || lineProperty.get() == LineTypeProperty.Type.Area) { + paint.bind(strokePaint); + } else if(flowConnections != null) { + strokePaint = null; + paint = null; + } + else paint.set(null); + + initializeShape(); + + if(coloringScheme != null) { + coloringScheme.addListener(new ChangeListener<Scheme>() { + @Override + public void changed(ObservableValue<? extends Scheme> observable, Scheme oldValue, Scheme newValue) { + + updateFlowColoring(); + flowPaint.getActiveProperty().set(newValue == Scheme.Common); + } + }); + } + } + + private void sendConnectionsBack() { + group.getChildren().remove(shape); + int index = group.getChildren().indexOf(shapes); + group.getChildren().add(index, shape); + } + + private void bringConnectionsFront() { + group.getChildren().remove(shape); + int index = group.getChildren().indexOf(shapes); + group.getChildren().add(index + 1, shape); + } + + @Override + public String getName() { + String name; + if(lineProperty.get() == LineTypeProperty.Type.None) name = "Collection"; + else if(lineProperty.get() == LineTypeProperty.Type.Area) name = "Area Chart"; + else name = "Line Chart"; + + return PropertyName.getPrefix(level) + " " + name; + } + + @Override + public String getType() { + if(lineProperty.get() == LineTypeProperty.Type.None) return "Collection"; + else if(lineProperty.get() == LineTypeProperty.Type.Area) return "AreaChart"; + else return "LineChart"; + } + + + protected void initializeShape() { + primitif = createShape(); + shape.add(primitif); + + ghost = primitifCopy(0, 0, true); + shape.addToGhost(ghost); + + updateConnections(); + + initControls(); + } + + @Override + protected Shape createShape() { + Path path = new Path(); + + switch(lineProperty.get()) { + case StraightSolid: + updateSolidLine(path, 0, 0); + break; + case Bezier: + updateBezier(path, 0, 0); + break; + case BezierSolid: + updateSolidBezier(path, 0, 0); + break; + case Straight: + updatePolyline(path, 0, 0); + break; + case TopBottom: + updateTopBottom(path, 0, 0); + break; + case Area: + updateArea(path, 0, 0); + break; + } + + return path; + } + + + protected boolean updateConnections() { + if(flowConnections == null) return false; + + Collection<FlowConnection> toremove= null; + for(FlowConnection flowConnection : connectionShapes.keySet()) { + if(!flowConnections.contains(flowConnection)) { + if(toremove == null) toremove = new ArrayList<>(); + toremove.add(flowConnection); + shape.remove(connectionShapes.get(flowConnection)); + } + } + if(toremove != null) { + for(FlowConnection conn: toremove) { + connectionShapes.remove(conn); + } + + toremove.clear(); + } + + for(FlowConnection flowConnection: flowConnections) { + flowConnection.getOrigin().resetConnectionFlags(); + flowConnection.getDestination().resetConnectionFlags(); + } + + Set<FlowConnection> sorted = flowConnections.sortByY(); + for(FlowConnection flowConnection: sorted) { + FlowPath path; + + if(!connectionShapes.keySet().contains(flowConnection)) { + path = new FlowPath(); + connectionShapes.put(flowConnection, path); + shape.add(path); + + FillColorProperty paintProperty = getFlowPaint(flowConnection); + path.fillProperty().bind(paintProperty); + path.strokeProperty().bind(paintProperty); + path.opacityProperty().bind(flowOpacity); + } else { + path = connectionShapes.get(flowConnection); + path.getElements().clear(); + } + + FlowCreator.createFlowConnectionPath(path, flowConnection); + } + + updateLabels(); + + return true; + } + + + protected Font labelFont = Font.font("Verdana", 10); + protected Color textColor = Color.GREY; + + public void addLabel(FlowConnection flowConnection) { + final Text text = new Text(); + text.setFont(labelFont); + text.setFill(textColor); + text.setTextAlignment(TextAlignment.CENTER); + text.setText(flowConnection.weight.get()+""); + + if(activeVariable != null && activeVariable.nodeShownProperty.get()) { + text.setVisible(true); + text.textProperty().set(activeVariable.transformationProperty().get().toData(flowConnection.weight.get())); + } else text.setVisible(false); + + connectionLabels.put(flowConnection, text); + shape.add(text); + } + + public void removeLabel(FlowConnection flowConnection) { + shape.remove(connectionLabels.get(flowConnection)); + connectionLabels.remove(flowConnection); + } + + public void updateLabels() { + + for(FlowConnection flowConnection: flowConnections) { + Text label = connectionLabels.get(flowConnection); + FlowPath path = connectionShapes.get(flowConnection); + + label.xProperty().set(path.centerX() - label.getLayoutBounds().getWidth()/2); + label.yProperty().set(path.centerY() + label.getLayoutBounds().getHeight()/2); + + if(activeVariable != null) label.textProperty().set(activeVariable.transformationProperty().get().toData(flowConnection.weight.get())); + } + } + + // Displays the labels for all flow connections + public void showLabels(DataVariable variable) { + activeVariable = variable; + + for(FlowConnection flowConnection: flowConnections) { + Text label = connectionLabels.get(flowConnection); + label.setVisible(variable.nodeShownProperty.get()); + } + + if(activeVariable.nodeShownProperty.get()) updateLabels(); + } + + + private void updateFlowColoring() { + for(FlowConnection flowConnection: flowConnections) { + Shape path = connectionShapes.get(flowConnection); + FillColorProperty paintProperty = getFlowPaint(flowConnection); + + if(connectionSelected == flowConnection) { + path.fillProperty().unbind(); + + paintProperty = new FillColorProperty(this, ((Color)paintProperty.get()).darker()); + path.fillProperty().bind(paintProperty); + } + else { + path.fillProperty().unbind(); + path.strokeProperty().unbind(); + path.fillProperty().bind(paintProperty); + path.strokeProperty().bind(paintProperty); + } + } + } + + private FillColorProperty getFlowPaint(FlowConnection connection) { + if(coloringScheme.get() == Scheme.Destination) return connection.getDestination().paint; + else if(coloringScheme.get() == Scheme.Source) return connection.getOrigin().paint; + return flowPaint; + } + + + @Override + public void updateGhost() { // TODO:.... + if(ghost == null) return; + + switch(lineProperty.get()) { + case StraightSolid: + updatePolyline((Path)ghost, 0, 0); + break; + case Bezier: + updateBezier((Path)ghost, 0, 0); + break; + case BezierSolid: + updateBezier((Path)ghost, 0, 0); + break; + case Straight: + updatePolyline((Path)ghost, 0, 0); + break; + case Area: + updatePolyline((Path)ghost, 0, 0); + break; + case TopBottom: + updateTopBottom((Path)ghost, 0, 0); + break; + default: + ((Path)ghost).getElements().clear(); + break; + } + } + + @Override + protected void refreshShape() { + if(updateConnections()) return; + + if(lineProperty == null || primitif == null) return; + + switch(lineProperty.get()) { + case StraightSolid: + paint.bind(strokePaint); + updateSolidLine((Path)primitif, 0, 0); + break; + case Bezier: + paint.unbind(); + paint.set(null); + updateBezier((Path)primitif, 0, 0); + break; + case BezierSolid: + paint.bind(strokePaint); + updateSolidBezier((Path)primitif, 0, 0); + break; + case Straight: + paint.unbind(); + paint.set(null); + updatePolyline((Path)primitif, 0, 0); + break; + case TopBottom: + paint.unbind(); + paint.set(null); + updateTopBottom((Path)primitif, 0, 0); + break; + case Area: + paint.bind(strokePaint); + updateArea((Path)primitif, 0, 0); + break; + default: + paint.unbind(); + paint.set(null); + ((Path)primitif).getElements().clear(); + break; + } + } + + + @Override + public SimpleMark createCopy(Container container, double dx, double dy) { + double x = coords.getX() + (curveSelected ? 0 : dx); + double y = coords.getY() + (curveSelected ? 0 : dy); + + LineConnectedCollection vgroup = new LineConnectedCollection(container, container.addToEnd(x, y)); + vgroup.setLevel(this.level); + + List<Mark> marks = new ArrayList<>(); + for(Mark mark: components) { + if(mark instanceof SimpleMark) { + SimpleMark simpleMark = (SimpleMark)mark; + SimpleMark copy = curveSelected ? simpleMark.createCopy(vgroup, dx, dy) : simpleMark.createCopy(vgroup, 0, 0); + copy.initProperties(); + marks.add(copy); + } + } + + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + vgroup.setRefCoords(refCoords); + + for(Mark mark: marks) { + vgroup.attachCopy(mark/*, childPropertyStructure.isXShared(), childPropertyStructure.isYShared()*/); + } + + vgroup.setPropertyStructure(PropertyStructure.create(marks, childPropertyStructure)); + + vgroup.toUpdate(false); // Protect the parent tree from property updates + vgroup.stealProperties(this); + vgroup.coords.x.set(x); + vgroup.coords.y.set(y); + vgroup.coords.xRef.set(RefX.Left); + vgroup.coords.yRef.set(RefY.Bottom); + vgroup.toUpdate(true); + + copyConnectionsTo(vgroup); + + return vgroup; + } + + + public void setFlowConnections(FlowConnections connections) { + this.flowConnections = connections; + + for(FlowConnection conn: connections) { + addLabel(conn); + conn.getOrigin().addOutwardConnection(conn); + conn.getDestination().addInwardConnection(conn); + } + + updateConnections(); + } + + // TODO: Problem when including groups!!!! + private void copyConnectionsTo(LineConnectedCollection vgroup) { + if(flowConnections == null) return; + + vgroup.coloringScheme.set(coloringScheme.get()); + vgroup.flowPaint.set(flowPaint.get()); + + vgroup.flowConnections = new FlowConnections(vgroup); + for(FlowConnection connection: flowConnections) { + ShapeMark source = connection.getOrigin(); + ShapeMark destination = connection.getDestination(); + + HierarchyPos posSource = new HierarchyPos(source); + HierarchyPos posDest = new HierarchyPos(destination); + + source = (ShapeMark) vgroup.getMarkAt(posSource); + destination = (ShapeMark) vgroup.getMarkAt(posDest); + + vgroup.createConnection(source, destination, false).weight.set(connection.weight.get()); + } + + vgroup.updateConnections(); + } + + @Override + protected Shape primitifCopy(double dx, double dy, boolean shadow) { + Path shape = new Path(); + + switch(lineProperty.get()) { + case StraightSolid: + if(shadow) updateBezier(shape, dx, dy); + else updateSolidLine(shape, dx, dy); + break; + case Bezier: + updateBezier(shape, dx, dy); + break; + case BezierSolid: + if(shadow) updateBezier(shape, dx, dy); + else updateSolidBezier(shape, dx, dy); + break; + case Straight: + if(shadow) updateBezier(shape, dx, dy); + else updatePolyline(shape, dx, dy); + break; + case TopBottom: + updateTopBottom(shape, dx, dy); + break; + case Area: + if(shadow) updateBezier(shape, dx, dy); + else updateArea(shape, dx, dy); + break; + } + + return shape; + } + + + private void changeShape(LineTypeProperty.Type newType) { + lineProperty.set(newType); + primitif = createShape(); + shape.replaceBy(primitif); + update(); + } + + @Override + public void stealProperties(SimpleMark mark) { + super.stealProperties(mark); + + if(mark instanceof LineConnectedCollection) { + LineConnectedCollection vgroup = (LineConnectedCollection)mark; + lineProperty = new LineTypeProperty(this, vgroup.lineProperty.get()); + } + } + + + private void updatePolyline(Path path, double dx, double dy) { + path.getElements().clear(); + + List<Double> points = new ArrayList<>(); + + for(Mark component: components) { + points.add(component.coords.getX() + dx); + points.add(-component.coords.getY() + dy); + } + + MoveTo moveTo = new MoveTo(); + moveTo.setX(points.get(0) + dx); + moveTo.setY(points.get(1) + dy); + path.getElements().add(moveTo); + + for(int i = 2; i < points.size(); i+=2) { + LineTo lineTo = new LineTo(); + lineTo.setX(points.get(i) + dx); + lineTo.setY(points.get(i+1) + dy); + path.getElements().add(lineTo); + } + } + + private void updateTopBottom(Path path, double dx, double dy) { + path.getElements().clear(); + List<Double> points1 = new ArrayList<>(); + List<Double> points2 = new ArrayList<>(); + + if(components.size() > 2) { + for(int i = 1 ; i < components.size(); ++i) { + points1.add(components.get(i - 1).left() + dx); + points1.add(-components.get(i - 1).top() + dy); + + points2.add(components.get(i).right() + dx); + points2.add(-components.get(i).bottom() + dy); + } + } + + for(int i = 0; i < points1.size(); i+=2) { + MoveTo moveTo = new MoveTo(); + moveTo.setX(points1.get(i) + dx); + moveTo.setY(points1.get(i + 1) + dy); + path.getElements().add(moveTo); + + LineTo lineTo = new LineTo(); + lineTo.setX(points2.get(i) + dx); + lineTo.setY(points2.get(i+1) + dy); + path.getElements().add(lineTo); + } + } + + private void updateBezier(Path path, double dx, double dy) { + path.getElements().clear(); + + List<Point2D> points = new ArrayList<>(); + for(Mark component: components) { + points.add(new Point2D(component.coords.getX() + dx, -component.coords.getY() + dy)); + } + + CubicSpline spline = new CubicSpline(path); + spline.setPoints(points); + } + + private void updateSolidBezier(Path path, double dx, double dy) { // TODO:... + path.getElements().clear(); + + List<Point2D> points = new ArrayList<>(); + for(Mark component: components) { + points.add(new Point2D(component.coords.getX() + dx, dy - component.top())); + } + for(int i = components.size() - 1; i >= 0; --i) { + Mark component = components.get(i); + points.add(new Point2D(component.coords.getX() + dx, dy - component.bottom())); + } + + //Mark component = components.get(0); + //points.add(new Point2D(component.coords.getX() + dx, -component.coords.getY() + dy + component.height()/2)); + + CubicSpline spline = new CubicSpline(path); + spline.setPoints(points); + + path.getElements().add(new ClosePath()); + } + + private void updateSolidLine(Path path, double dx, double dy) { + path.getElements().clear(); + + double[] points = new double[components.size()*4]; + int index = 0; + for(int i = 0; i < components.size(); i++) { + points[index++] = components.get(i).coords.getX(); + points[index++] = -components.get(i).top();//-components.get(i).coords.getY() + components.get(i).height()/2; + } + for(int i = components.size() - 1; i >= 0; i--) { + points[index++] = components.get(i).coords.getX(); + points[index++] = -components.get(i).bottom();//-components.get(i).coords.getY() - components.get(i).height()/2; + } + + MoveTo moveTo = new MoveTo(); + moveTo.setX(points[0] + dx); + moveTo.setY(points[1] + dy); + path.getElements().add(moveTo); + + for(int i = 2; i < points.length; i+=2) { + LineTo lineTo = new LineTo(); + lineTo.setX(points[i] + dx); + lineTo.setY(points[i+1] + dy); + path.getElements().add(lineTo); + } + + path.getElements().add(new ClosePath()); + } + + + private void updateArea(Path path, double dx, double dy) { + path.getElements().clear(); + + List<Double> points = new ArrayList<>(); + + for(Mark component: components) { + points.add(component.coords.getX() + dx); + points.add(-component.coords.getY() + dy); + } + points.add(components.get(components.size() - 1).coords.getX() + dx); + points.add((double) 0); + + points.add(components.get(0).coords.getX() + dx); + points.add((double) 0); + + MoveTo moveTo = new MoveTo(); + moveTo.setX(points.get(0) + dx); + moveTo.setY(points.get(1) + dy); + path.getElements().add(moveTo); + + for(int i = 2; i < points.size(); i+=2) { + LineTo lineTo = new LineTo(); + lineTo.setX(points.get(i) + dx); + lineTo.setY(points.get(i+1) + dy); + path.getElements().add(lineTo); + } + + path.getElements().add(new ClosePath()); + } + + @Override + public Shadow getShadow(double dx, double dy, double delta) { + if(curveSelected) { + double d = dy*delta; + return new Shadow(this, 0, d, primitifCopy(0, d, true)); + } + else return super.getShadow(dx, dy, delta); + } + + + /* + * This is to create arbitrary connection between the nodes of a top collection + */ + public FlowConnection createConnection(ShapeMark origin, ShapeMark destination, boolean clever) { + if(flowConnections == null) { + childPropertyStructure.removeHeightSharing(origin); + + lineProperty.set(Type.None); + lineProperty.getActiveProperty().set(false); + + strokePaint = null; + paint = null; + flowConnections = new FlowConnections(this); + } + + FlowConnection connection = flowConnections.addConnection(origin, destination, clever); + addLabel(connection); + + if(connection != null) { + updateConnections(); + origin.addOutwardConnection(connection); + destination.addInwardConnection(connection); + } + + origin.updateLabels(); + + return connection; + } + + + public void removeConnection(FlowConnection flowConnection) { + flowConnections.remove(flowConnection); + Shape path = connectionShapes.get(flowConnection); + shape.remove(path); + connectionShapes.remove(flowConnection); + + removeLabel(flowConnection); + } + + public FlowConnections getFlowConnections() { + return flowConnections; + } + + @Override + public boolean hasConnections() { + return flowConnections != null && !flowConnections.isEmpty(); + } + + + public boolean containtsConnection(ShapeMark origin, ShapeMark destination) { + return(flowConnections != null && flowConnections.contains(new FlowConnection(this, origin, destination))); + } + + + public boolean hasCycle(ShapeMark from, ShapeMark to) { + if(flowConnections == null) return false; + else if(from == to) return true; + + Set<FlowConnection> connections = flowConnections.from(from); + if(connections.isEmpty()) return false; + + for(FlowConnection conn: connections) { + if(hasCycle(conn.getDestination(), to)) return true; + } + + return false; + } + + + @Override + public void translate(Line lineTrace) { + if(connectionSelected == null) { + super.translate(lineTrace); + updateExtraComponents(); + } else { + double x = pinX + lineTrace.getEndX() - lineTrace.getStartX() - connectionSelected.getOrigin().coords.getX(); + double y = pinY - lineTrace.getEndY() + lineTrace.getStartY() - connectionSelected.getOrigin().coords.getY(); + + + Mark mark = connectionSelected.getDestination(); + double xdest = x + mark.coords.getX(); + double ydest = y + mark.coords.getY(); + + mark = connectionSelected.getOrigin(); + mark.coords.x.updateValue(x + mark.coords.getX()); + mark.coords.y.updateValue(y + mark.coords.getY()); + + mark = connectionSelected.getDestination(); + mark.coords.x.updateValue(xdest); + mark.coords.y.updateValue(ydest); + + updateInteractor(); + updateExtraComponents(); + } + } + + + @Override + public void setHighlight(boolean highlight, boolean enableControllers) { + if(connectionSelected == null) super.setHighlight(highlight, enableControllers); + else { + if(!highlight) { + connectionSelected = null; + updateFlowColoring(); + } + } + } + + public boolean isConnectionSelected() { + return connectionSelected != null; + } + + public void fullDelete() { + if(connectionSelected != null) { + removeConnection(connectionSelected); + connectionSelected.detachFront(); + connectionSelected.detachBack(); + + connectionSelected = null; + + updateConnections(); + } + else { + setHighlight(false, true); + super.fullDelete(); + } + + } + + @Override + public void pin() { + if(connectionSelected == null) super.pin(); + } + + public void selectConnection(FlowConnection connection) { + if(connection != null) { + pinX = connection.getOrigin().coords.getX(); + pinY = connection.getOrigin().coords.getY(); + } + connectionSelected = connection; + updateFlowColoring(); + } + + @Override + public boolean monoselect(MarkSelection selectedMarks, Circle trace) { + // Handling line connections + if(lineProperty != null && lineProperty.get() != LineTypeProperty.Type.None) { + curveSelected = false; + + boolean consumed = super.monoselect(selectedMarks, trace); + if(consumed) { + return consumed; + } + + if(shape.intersects(trace) && components.size() > 0) { + pinX = components.get(0).coords.getX(); + pinY = components.get(0).coords.getY(); + + selectedMarks.add(this); + + curveSelected = true; + + return true; + } + + return false; + } + else if(flowConnections == null || trace == null) { + return super.monoselect(selectedMarks, trace); + } + else { + // Handling Flow connections + boolean consumed = super.monoselect(selectedMarks, trace); + + if(consumed) { + if(connectionSelected != null) { + connectionSelected = null; + updateFlowColoring(); + } + return consumed; + } + + for(FlowConnection connection:flowConnections) { + Shape path = connectionShapes.get(connection); + Shape intersection = Shape.intersect(path, trace); + if(!(intersection instanceof Path) || !((Path)intersection).getElements().isEmpty()) { + selectedMarks.add(this); + + pinX = connection.getOrigin().coords.getX(); + pinY = connection.getOrigin().coords.getY(); + connectionSelected = connection; + + updateFlowColoring(); + return true; + } + } + + connectionSelected = null; + updateFlowColoring(); + + return super.monoselect(selectedMarks, trace); + } + } + + @Override + public boolean controlselect(MarkSelection selectedMarks, Circle trace) { + if(flowConnections != null) { + connectionSelected = null; + updateFlowColoring(); + } + + return super.controlselect(selectedMarks, trace); + } + + + @Override + public Property getProperty(PropertyName name) { + if(name.isCurve()) return lineProperty; + else if(name.isOpacity()) return flowOpacity; + else if(name.isColoring()) return coloringScheme; + else if(name.isFill()) return flowPaint; + else return super.getProperty(name); + } +} diff --git a/src/fr/inria/structgraphics/graphics/Mark.java b/src/fr/inria/structgraphics/graphics/Mark.java new file mode 100644 index 0000000000000000000000000000000000000000..7a705b86e2f3aa48580a245708f372652a85bfee --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/Mark.java @@ -0,0 +1,832 @@ +package fr.inria.structgraphics.graphics; + +import java.util.List; +import java.util.Map; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.graphics.controls.Control; +import fr.inria.structgraphics.types.FillColorProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.HeightProperty; +import fr.inria.structgraphics.types.OrderProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.RatioLockProperty; +import fr.inria.structgraphics.types.RatioProperty; +import fr.inria.structgraphics.types.RotationProperty; +import fr.inria.structgraphics.types.StrokeColorProperty; +import fr.inria.structgraphics.types.StrokeWidthProperty; +import fr.inria.structgraphics.types.WidthProperty; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.tools.SelectFilter; +import fr.inria.structgraphics.ui.utils.BidirectionalBindings; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.ConstraintController; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.LabelCollection; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.ConstraintController.ConstraintHandle; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Shape; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; +import javafx.util.Pair; + +public abstract class Mark extends Container { + + public WidthProperty width; + public HeightProperty height; + + public RatioProperty hwratio; + public RatioLockProperty ratiolock; + + protected boolean highlighted = false; + + protected double pinX, pinY; + + public PositionCoords coords; + protected Container container; + + protected ComplexShape shape = new ComplexShape(); + + protected Group controls = new Group(); + protected Control selectedControl = null; + + protected LabelCollection labels; + + // Distribution constraints + protected ConstraintController constraintController = null; + protected ConstraintController.ConstraintHandle constraintHandler = null; + + protected int level = 0; + + public StrokeWidthProperty border = new StrokeWidthProperty(this, 0); + public FillColorProperty paint = new FillColorProperty(this); + public StrokeColorProperty strokePaint = new StrokeColorProperty (this); + public RotationProperty angle = new RotationProperty(this); + + //protected DoubleProperty distance = new SimpleDoubleProperty(), gap = new SimpleDoubleProperty(); + + public Mark(Container container, boolean toend, double w, double h){ + super(); + + if(container != null) { + container.addMark(this, toend); + container.refreshID(); + } + + group.getChildren().add(shape); + group.getChildren().add(controls); + controls.setVisible(false); + + labels = new LabelCollection(this); + group.getChildren().add(labels); + + this.width = new WidthProperty(this, w); + this.height = new HeightProperty(this, h); + this.hwratio = new RatioProperty(this, "h-w ratio", w == 0 ? 1 : h/w); + this.ratiolock = new RatioLockProperty(this, "lock", false); + + /* + * Support ratio dependencies between height and width + */ + ChangeListener<Number> widthListener = new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + hwratio.set(height.doubleValue()/newValue.doubleValue()); + } + }; + ChangeListener<Number> heightListener = new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + hwratio.set(newValue.doubleValue()/width.doubleValue()); + } + }; + width.addListener(widthListener); + height.addListener(heightListener); + + ratiolock.addListener(new ChangeListener<Boolean>() { + private Pair<ChangeListener<Number>, ChangeListener<Number>> listeners = null; + + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + if(newValue) { + width.removeListener(widthListener); + height.removeListener(heightListener); + + listeners = BidirectionalBindings.bindBidirectional(width, height, + new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + height.set(newValue.doubleValue()*hwratio.get()); + } + }, + new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + width.set(newValue.doubleValue()/hwratio.get()); + } + } + ); + } else { + // TODO: remove binding and add the following + BidirectionalBindings.unbindBidirectional(width, height, listeners); + + width.addListener(widthListener); + height.addListener(heightListener); + } + } + }); + + } + + public void addSelfProperties(boolean toCollection, Map<PropertyName, FlexibleListProperty> table) { // TODO: Name IDS are fucked up !!!! + for(Property property: properties) { + FlexibleListProperty list = table.get(new PropertyName(PropertyName.getCleanName(property.getName()))); + if(list == null) { + list = FlexibleListProperty.createList(this, property); + table.put(new PropertyName(list.getName()), list); + } + else { + if(isFirst()) list.add(0, property); + else list.add(property); + } + } + } + + + public boolean removeSelfProperties(Map<PropertyName, FlexibleListProperty> table) { + boolean removed = false; + for(Property property: properties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + + if(list != null) { + if(list.removeProperty(property)) { + removed = true; + } + } + } + + return removed; + } + + + public Mark(Container container){ + this(container, true, 0, 0); + } + + public boolean isFirst() { + return container.components.indexOf(this) == 0; + } + + @Override + public void addLeafIDs(List<Property> ids){ + if(components.isEmpty()) ids.add(id); + else super.addLeafIDs(ids); + } + + + @Override + public void addIDs(List<Property> ids, int level) { + if(components.isEmpty()) { + if(this.level == level) ids.add(id); + else if(container instanceof VisCollection) ((VisCollection)container).addID(ids, level); + } + else super.addIDs(ids, level); + } + + public int getBottomLevel() { + if(components.isEmpty()) return level; + Mark component = components.get(0); + return component.getBottomLevel(); + } + + @Override + public void initProperties() { + super.initProperties(); + + if(coords != null) { + addProperty(coords.getXRefProperty()); + addProperty(coords.getYRefProperty()); + + Mark mark = this; + + coords.xRef.addListener(new ChangeListener<RefX>() { + @Override + public void changed(ObservableValue<? extends RefX> observable, RefX oldValue, RefX newValue) { + if(container instanceof VisBody) { + ((VisBody) container).invalidateConstraints(mark); + ((VisBody)container).updateInteractor(); + return; + } + + double dx = 0; + + switch(oldValue) { + case Center: + if(newValue == RefX.Left) dx = -width.get()/2; + else if(newValue == RefX.Right) dx = width.get()/2; + break; + case Left: + if(newValue == RefX.Center) dx = width.get()/2; + else if(newValue == RefX.Right) dx = width.get(); + break; + case Right: + if(newValue == RefX.Center) dx = -width.get()/2; + else if(newValue == RefX.Left) dx = -width.get(); + break; + default: break; + } + + coords.x.set(coords.getX() + dx); + container.updateAll(); + } + }); + coords.yRef.addListener(new ChangeListener<RefY>() { + @Override + public void changed(ObservableValue<? extends RefY> observable, RefY oldValue, RefY newValue) { + if(container instanceof VisBody) { + ((VisBody) container).invalidateConstraints(mark); + ((VisBody)container).updateInteractor(); + return; + } + + double dy = 0; + + switch(oldValue) { + case Center: + if(newValue == RefY.Bottom) dy = -height.get()/2; + else if(newValue == RefY.Top) dy = height.get()/2; + break; + case Bottom: + if(newValue == RefY.Center) dy = height.get()/2; + else if(newValue == RefY.Top) dy = height.get(); + break; + case Top: + if(newValue == RefY.Center) dy = -height.get()/2; + else if(newValue == RefY.Bottom) dy = -height.get(); + break; + default: break; + } + + coords.y.set(coords.getY() + dy); + container.updateAll(); + } + }); + + //addProperty(new DummyProperty(this)); + + addProperty(coords.getXProperty()); + addProperty(coords.getYProperty()); + } + + /* + coords.x.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + List<Mark> siblings = container.getComponents(); + + if(container instanceof VisCollection && ((VisCollection) container).constraintXProperty.get() == Constraint.None) { + ((VisCollection)container).reorderChildren(true); + } + } + });*/ + } + + protected String createID() { + int pos = container.getComponents().indexOf(this) + 1; + if(container instanceof Mark) return ((Mark) container).createID() + "." + pos; + else return "" + pos; + } + + @Override + public void refreshID() { + id.set(createID()); + + for(Mark mark:components) + mark.refreshID(); + } + + public void setLevel(int level) { + this.level = level; + } + + public int getLevel() { + return level; + } + + protected void initControls() { + + } + + public void dispose() { + group.getChildren().remove(labels); + container.removeMark(this); + group.getChildren().remove(shape); // TODO: Is this OK? + } + + public void updateLabels() { + labels.update(); + } + + public boolean exists() { + return container != null; + } + + public void fullDelete() { + setHighlight(false, true); + + dispose(); + + if(container instanceof VisBody) { + if(container.getComponents().isEmpty()) + ((VisBody) container).fullDelete(); + } + + container = null; + } + + public void setCoords(PositionCoords coords) { + this.coords = coords; + } + + public void setPositionCoords(double x, double y) { + coords.x.updateValue(x); + coords.y.updateValue(y); + } + + public void setPositionX(double x) { + coords.x.set(x); + } + + public void setPositionY(double y) { + coords.y.set(y); + } + + + public PositionCoords getCoords() { + return coords; + } + + public void setBorder(int border) { + this.border.setValue(border);; + } + + public void setAngle(int angle) { + this.angle.set(angle); + } + + public abstract double width(); + public abstract double height(); + + public double left() { + if(coords.getXRef() == RefX.Center) return coords.getX() - width()/2; + else if(coords.getXRef() == RefX.Right) return coords.getX() - width(); + else return coords.getX(); + } + + public double right() { + if(coords.getXRef() == RefX.Center) return coords.getX() + width()/2; + else if(coords.getXRef() == RefX.Left) return coords.getX() + width(); + else return coords.getX(); + } + + public double centerX() { + if(coords.getXRef() == RefX.Center) return coords.getX(); + else if(coords.getXRef() == RefX.Left) return coords.getX() + width()/2; + else return coords.getX() - width()/2; + } + + public double top() { + if(coords.getYRef() == RefY.Center) return coords.getY() + height()/2; + else if(coords.getYRef() == RefY.Bottom) return coords.getY() + height(); + else return coords.getY(); + } + + public double bottom() { + if(coords.getYRef() == RefY.Center) return coords.getY() - height()/2; + else if(coords.getYRef() == RefY.Bottom) return coords.getY(); + else return coords.getY() - height(); + } + + public double centerY() { + if(coords.getYRef() == RefY.Center) return coords.getY(); + else if(coords.getYRef() == RefY.Bottom) return coords.getY() + height()/2; + else return coords.getY() - height()/2; + } + + public void setFill(String fillname) { + this.paint.set(Fill.getPaint(fillname)); + } + + public void setFillColor(String fillname) { + if(fillname != null) + this.paint.set(Color.web(fillname)); + else this.paint.set(null); + } + + public void setStrokeColor(String colorname) { + this.strokePaint.set(Color.web(colorname)); + } + + public Container getContainer(){ + return container; + } + + public ComplexShape getShape(){ + return shape; + } + + protected abstract void updateBasicShape(); + + @Override + public void clear() { + super.clear(); + // shape.clear(); + } + + protected Point2D getRotationPoint() { + double cx = 0, cy = 0; + + // X axis + if(coords.getXRef() == RefX.Left) cx = -width.get()/2; + else if(coords.getXRef() == RefX.Right) cx = width.get()/2; + else cx = 0; + + // Y axis + if(coords.getYRef() == RefY.Bottom) cy = height.get()/2; + else if(coords.getYRef() == RefY.Top) cy = -height.get()/2; + else cy = 0; + + return new Point2D(cx, cy); + } + + public void updateShape(double cx, double cy) { + Point2D p = container.getReferencePoint(cx, cy); + + double xref = p.getX(); + double yref = p.getY(); + + double refAngle = -container.getRefAngle(cx); + if(refAngle != 0) group.getTransforms().add(new Rotate(refAngle, xref, yref)); + //if(angle.get() != 0) group.getTransforms().add(new Rotate(angle.get(), xref, yref)); + + //updateBasicShape(); + + group.getTransforms().add(new Translate(xref, yref)); + updateBasicShape(); + + Point2D r = getRotationPoint(); + if(angle.get() != 0) group.getTransforms().add(new Rotate(angle.get(), r.getX(), r.getY())); + + if(paint != null) shape.setFill(paint.get()); + //else shape.setFill(null); + + if(strokePaint != null) { + shape.setStroke(strokePaint.get()); + shape.setStrokeWidth(border.get()); + } + //else shape.setStroke(Color.DARKGRAY); + + } + + + @Override + public double getRefAngle(double x) { + return 0; + } + + @Override + public String getName() { + return "General Mark"; + } + + @Override + public String getType() { + return "Graphic"; + } + + @Override + public void select(MarkSelection selectedMarks, Shape trace, SelectFilter filter) { + if(!filter.accept(selectedMarks.getMarks(), this)) return; + + if(shape.intersects(trace)) { + pinX = coords.getX(); + pinY = coords.getY(); + selectedMarks.add(this); + } + + super.select(selectedMarks, trace, filter); + } + + + public boolean controlselect(MarkSelection selectedMarks, Circle trace) { + selectedControl = null; + if(controls.isVisible()) { + for(Node node: controls.getChildren()) { + if(((Control)node).intersects(trace)) { + selectedControl = (Control)node; + selectedControl.pin(); + selectedMarks.add(this); + return true; + } + } + } + + return false; + } + + + public void pin() { + pinX = coords.getX(); + pinY = coords.getY(); + } + + @Override + public boolean monoselect(MarkSelection selectedMarks, Circle trace) { + if(super.monoselect(selectedMarks, trace)) return true; + + if(shape.intersects(trace)) { + pin(); + + selectedMarks.add(this); + return true; + } + else return false; + } + + + public void setHighlight(boolean highlight, boolean enableControls) { + this.highlighted = highlight; + + shape.setHighlight(highlight); + if(enableControls) controls.setVisible(highlight); + + //if(highlight) controls.setVisible(enableControls); + //else controls.setVisible(false); + } + + public boolean isHighlighted() { + return highlighted; + } + + public Mark getHighlightedChild() { + return highlighted ? this : null; + } + + public void setConstraintControl(ConstraintController constraintController, ConstraintHandle constraintHandler) { + this.constraintController = constraintController; + this.constraintHandler = constraintHandler; + } + + + public void translate(Line lineTrace) { + if(constraintController != null) { + constraintController.translate(lineTrace, constraintHandler); + } + else if(selectedControl != null) { + selectedControl.drag(lineTrace); + } else { + double dx = lineTrace.getEndX() - lineTrace.getStartX(); + coords.x.updateValue(pinX + dx); + coords.y.updateValue(pinY - lineTrace.getEndY() + lineTrace.getStartY()); + } + } + + public boolean hasParent(Container node) { + if(node == container) return true; + else if(container instanceof Mark) return ((Mark)container).hasParent(node); + else return false; + } + + public Container getRoot() { + if(container instanceof Mark) return ((Mark)container).getRoot(); + else return container; + } + + + public double getLeft() { + double x = coords.getX(); + double w = width.get(); + + switch(coords.getXRef()) { + case Left: return (w>=0) ? x : x + w; + case Right: return (w>=0) ? x - w : x; + default: return (w>=0) ? x - w/2 : x + w/2; + } + } + + public double getRight() { + double x = coords.getX(); + double w = width.get(); + + switch(coords.getXRef()) { + case Left: return (w>=0) ? x + w : x; + case Right: return (w>=0) ? x : x - w; + default: return (w>=0) ? x + w/2 : x - w/2; + } + } + + public double getTop() { + double y = coords.getY(); + double h = height.get(); + + switch(coords.getYRef()) { + case Bottom: return (h>=0) ? -y - h : -y; + case Top: return (h>=0) ? -y : -y + h; + default: return (h>=0) ? -y - h/2 : -y + h/2; + } + } + + public double getBottom() { + double y = coords.getY(); + double h = height.get(); + + switch(coords.getYRef()) { + case Bottom: return (h>=0) ? -y : -y - h; + case Top: return (h>=0) ? -y + h : -y; + default: return (h>=0) ? -y + h/2 : -y - h/2; + } + } + + public double getGlobalX() { + return (container instanceof Mark) ? + coords.getX() + ((Mark)container).getGlobalX() : coords.getX(); + } + + public double getGlobalY() { + return (container instanceof Mark) ? + coords.getY() + ((Mark)container).getGlobalY() : coords.getY(); + } + + public double getGlobalLeft() { + return (container instanceof Mark) ? + getLeft() + ((Mark)container).getGlobalX() : getLeft(); + } + + public double getGlobalBottom() { + return (container instanceof Mark) ? + getBottom() - ((Mark)container).getGlobalY() : getBottom(); + } + + public double getGlobalRight() { + return (container instanceof Mark) ? + getRight() + ((Mark)container).getGlobalX() : getRight(); + } + + public double getGlobalTop() { + return (container instanceof Mark) ? + getTop() - ((Mark)container).getGlobalY() : getTop(); + } + + public double getRootWidth() { + return container.getRootWidth(); + } + + public double getRootHeight() { + return container.getRootHeight(); + } + + + public String getNestingPropertyName(String name) { + VisGroup topGroup = getTopGroup(); + if(topGroup == null) return name; + else { + String id = createID(); + String pid = topGroup.createID() + "."; + + return name + id.replaceFirst(pid, ""); + } + } + + public VisGroup getTopGroup() { + if(container instanceof VisGroup) return ((VisGroup) container).getTopGroup(); + else return null; + } + + @Override + public VisBody getRootVirtualGroup() { + if(container instanceof VisBody) + return ((VisBody) container).getRootVirtualGroup(); + else return null; + } + + public VisBody getVirtualGroup() { + return container.getVirtualGroup(); + } + + //public void showLabel(DataVariable variable, Property property) {} + + //@Override + public void showLabel(DataVariable variable, Property property) { + if(isLabelShown(variable) == variable.nodeShownProperty.get()) return; + labels.showVariable(variable, property); + } + + public boolean isLabelShown(DataVariable variable) { + return labels.isLabelShown(variable); + } + + + public void mouseRelease() { + VisBody vgroup = getVirtualGroup(); + if(vgroup != null) getVirtualGroup().mouseRelease(); + } + + public void changeXReference(RefX oldValue, RefX newValue, double w) { + if(oldValue == RefX.Left && newValue == RefX.Center || oldValue == RefX.Center && newValue == RefX.Right) { + coords.x.set(coords.getX() - w/2); + } + else if(oldValue == RefX.Left && newValue == RefX.Right) { + coords.x.set(coords.getX() - w); + } + else if(oldValue == RefX.Center && newValue == RefX.Left || oldValue == RefX.Right && newValue == RefX.Center) { + coords.x.set(coords.getX() + w/2); + } + else if(oldValue == RefX.Right && newValue == RefX.Left) { + coords.x.set(coords.getX() + w); + } + } + + public void changeYReference(RefY oldValue, RefY newValue, double h) { + if(oldValue == RefY.Bottom && newValue == RefY.Center || oldValue == RefY.Center && newValue == RefY.Top) { + coords.y.set(coords.getY() - h/2); + } + else if(oldValue == RefY.Bottom && newValue == RefY.Center) { + coords.y.set(coords.getY() - h); + } + else if(oldValue == RefY.Center && newValue == RefY.Bottom || oldValue == RefY.Top && newValue == RefY.Center) { + coords.y.set(coords.getY() + h/2); + } + else if(oldValue == RefY.Top && newValue == RefY.Bottom) { + coords.y.set(coords.getY() + h); + } + } + + /* + * This is a mechanism for updating the coordinates of the marks without undesirable double binding activations + */ + protected double tentative_x = 0, tentative_y = 0; + public void setTentative(double x, double y) { + tentative_x = x; + tentative_y = y; + } + + public void validateTentative() { + if(coords.x.get() != tentative_x) coords.x.set(tentative_x); + if(coords.y.get() != tentative_y) coords.y.set(tentative_y); + } + + public int getPos() { + return container.components.indexOf(this); + } + + public Mark getNodeWithId(String id) { + if(id.equals(this.id.get())) return this; + + for(Mark child : components) { + Mark mark = child.getNodeWithId(id); + if(mark != null) return mark; + } + + return null; + } + + public Property getProperty(PropertyName name) { + if(name.isX()) return coords.x; + else if(name.isY()) return coords.y; + else if(name.isHeight()) return height; + else if(name.isWidth()) return width; + else if(name.isFill()) return paint; + else if(name.isRotation()) return angle; + else if(name.isThickness()) return border; + else if(name.isStroke()) return strokePaint; + else if(name.isReferenceX()) return coords.xRef; + else if(name.isReferenceY()) return coords.yRef; + else return null; + } + + public Container getComponent(String sid){ + if(id.get().equals(sid)) return this; + else return super.getComponent(sid); + } + + + @Override + public double getYIn(Container container) { + if(this.container == container) return coords.getY(); + else return coords.getY() + this.container.getYIn(container); + } + + @Override + public double getXIn(Container container) { + if(this.container == container) return coords.getX(); + else return coords.getX() + this.container.getXIn(container); + } +} diff --git a/src/fr/inria/structgraphics/graphics/MarkFactory.java b/src/fr/inria/structgraphics/graphics/MarkFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..e1c6b11dcd9d266f174ce8289df378475650de3f --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/MarkFactory.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.graphics; + +public class MarkFactory { + + public static VisFrame createVisFrame(double width, double height) { + VisFrame visFrame = new VisFrame(width, height); + visFrame.setRefCoords(new ReferenceCoords("left", "bottom")); + + return visFrame; + } + +} diff --git a/src/fr/inria/structgraphics/graphics/MultiplyFeedForward.java b/src/fr/inria/structgraphics/graphics/MultiplyFeedForward.java new file mode 100644 index 0000000000000000000000000000000000000000..11656fa50a932d419f7e705520733cc7c3fc809e --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/MultiplyFeedForward.java @@ -0,0 +1,95 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.List; + +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.shape.Line; + +public class MultiplyFeedForward extends Group { + + private double gapx = 20; + private double gapy = 10; + private int maxX = 20; + private int maxY = 20; + + private SimpleMark mark; + + public MultiplyFeedForward(SimpleMark mark) { + super(); + this.mark = mark; + + if(mark instanceof VisBody) { + maxX = 11; + maxY = 11; + } + + build(); + + } + + private void build() { + ArrayList<Point2D> positions = MultiplyUtils.getAvailablePositions(mark); + if(positions == null) { + buildalternative(); + return; + } + + for(Point2D pos: positions) { + getChildren().add(mark.getShadow(pos.getX(), pos.getY(), 1)); + } + } + + private void buildalternative() { + double W = mark.getRootWidth(); + double H = mark.getRootHeight(); + double left = mark.getGlobalLeft(); + double right = mark.getGlobalRight(); + double top = -mark.getGlobalTop(); + double bottom = -mark.getGlobalBottom(); + + double dx = Math.abs(mark.width()) + gapx; + // TODO: For copying the line???? + + boolean linechart = (mark instanceof LineConnectedCollection) && ((LineConnectedCollection)mark).curveSelected; + + double dy = linechart ? 50 : Math.abs(mark.height()) + gapy; + + if(!linechart) { + for(int i = 1; right + dx*i < W && i < maxX; ++ i) { + getChildren().add(mark.getShadow(i, 0, dx)); + } + + for(int i = 1; left - dx*i > 0 && i < maxX; ++ i) { + getChildren().add(mark.getShadow(-i, 0, dx)); + } + } + + for(int i = 1; top + dy*i < H && i < maxY; ++ i) { + getChildren().add(mark.getShadow(0, -i, dy)); + } + + for(int i = 1; bottom - dy*i > 0 && i < maxY; ++ i) { + getChildren().add(mark.getShadow(0, i, dy)); + } + + } + + + public List<Shadow> select(Line trace) { + List<Shadow> selected = new ArrayList<Shadow>(); + if(trace == null || trace.getStartX() == trace.getEndX() && trace.getStartY() == trace.getEndY()) return selected; + + for(Node node:getChildren()) { + Shadow shadow = (Shadow)node; + if(shadow.intersects(trace)) + selected.add(shadow); + } + + return selected; + } + + +} diff --git a/src/fr/inria/structgraphics/graphics/MultiplyUtils.java b/src/fr/inria/structgraphics/graphics/MultiplyUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..bf21fb12a0db00412a49f3b2fe95186fc6e8579a --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/MultiplyUtils.java @@ -0,0 +1,115 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; + +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import javafx.geometry.Point2D; + +public class MultiplyUtils { + + public static ArrayList<Point2D> getAvailablePositions(Mark mark){ + VisBody visbody; + + if(!(mark.getContainer() instanceof VisBody)) { + return null; + } else visbody = (VisBody) mark.getContainer(); + + double W = visbody.getRootWidth(); + double H = visbody.getRootHeight(); + + ArrayList<Point2D> positions = new ArrayList<>(); + + // TODO: I do not currently consider the case where I have constraints in both x and y directions + if(visbody.constraintXProperty.get() != Constraint.None) { + // first do the ones on the right + Mark last = visbody.getComponentAt(visbody.components.size() - 1); + if(visbody.constraintXProperty.get() == Constraint.Distance) { + double xpos = last.getGlobalX() + visbody.distanceXProperty.get(); + + while(xpos + mark.width() < W) { // TODO: Not correct.... + positions.add(new Point2D(xpos - mark.getGlobalX(), 0)); + xpos += visbody.distanceXProperty.get(); + } + } else { + double xpos = last.getGlobalX() + Math.abs(last.width()) + visbody.distanceXProperty.get(); + double dx = Math.abs(mark.width()) + visbody.distanceXProperty.get(); + + while(xpos + mark.width() < W) { + positions.add(new Point2D(xpos - mark.getGlobalX(), 0)); + xpos += dx; + } + } + + // And then the ones on the left + if(visbody.alignXProperty.get() == YSticky.No) { + Mark first = visbody.getComponentAt(0); + if(visbody.constraintXProperty.get() == Constraint.Distance) { + double xpos = first.getGlobalX() - visbody.distanceXProperty.get(); + + while(xpos - mark.width() > 0) { // TODO: Not correct.... + positions.add(new Point2D(xpos - mark.getGlobalX(), 0)); + xpos -= visbody.distanceXProperty.get(); + } + } else { + double xpos = first.getGlobalX() - Math.abs(mark.width()) - visbody.distanceXProperty.get(); + double dx = -Math.abs(mark.width()) - visbody.distanceXProperty.get(); + + while(xpos - mark.width() > 0) { + positions.add(new Point2D(xpos - mark.getGlobalX(), 0)); + xpos += dx; + } + } + } + + } else if(visbody.constraintYProperty.get() != Constraint.None) { + // first do the ones on the right + Mark last = visbody.getComponentAt(visbody.components.size() - 1); + if(visbody.constraintYProperty.get() == Constraint.Distance) { + double ypos = last.getGlobalY() + visbody.distanceYProperty.get(); + + while(ypos + mark.height() < H) { // TODO: Not correct.... + positions.add(new Point2D(0, -ypos + mark.getGlobalY())); + ypos += visbody.distanceYProperty.get(); + } + } else { + double ypos = last.getGlobalY() + Math.abs(last.height()) + visbody.distanceYProperty.get(); + double dy = Math.abs(mark.height()) + visbody.distanceYProperty.get(); + + while(ypos + mark.height() < H) { + positions.add(new Point2D(0, -ypos + mark.getGlobalY())); + ypos += dy; + } + } + + + // And then the ones on the BOTTOM + if(visbody.alignYProperty.get() == XSticky.No) { + Mark first = visbody.getComponentAt(0); + if(visbody.constraintYProperty.get() == Constraint.Distance) { + double ypos = first.getGlobalY() - visbody.distanceYProperty.get(); + + while(ypos - mark.height() > 0) { // TODO: Not correct.... + positions.add(new Point2D(0, -ypos + mark.getGlobalY())); + ypos -= visbody.distanceYProperty.get(); + } + } else { + double ypos = first.getGlobalY() - Math.abs(mark.height()) - visbody.distanceYProperty.get(); + double dy = -Math.abs(mark.height()) - visbody.distanceYProperty.get(); + + while(ypos - mark.height() > 0) { + positions.add(new Point2D(0, -ypos + mark.getGlobalY())); + ypos += dy; + } + } + } + + } // TODO: To deal with x, y hsaring constrraints.... + else { + positions = null; + } + + return positions; + } +} diff --git a/src/fr/inria/structgraphics/graphics/PositionCoords.java b/src/fr/inria/structgraphics/graphics/PositionCoords.java new file mode 100644 index 0000000000000000000000000000000000000000..12c06ecfda0973a2ebf834d1acd351ede362e86b --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/PositionCoords.java @@ -0,0 +1,67 @@ +package fr.inria.structgraphics.graphics; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.ComponentRefXProperty; +import fr.inria.structgraphics.types.ComponentRefYProperty; +import fr.inria.structgraphics.types.XProperty; +import fr.inria.structgraphics.types.YProperty; + +public class PositionCoords { + + public XProperty x; + public YProperty y; + + public ComponentRefXProperty xRef; + public ComponentRefYProperty yRef; + + public PositionCoords(Mark mark) { + this.x = new XProperty(mark); + this.y = new YProperty(mark); + xRef = new ComponentRefXProperty(mark); + yRef = new ComponentRefYProperty(mark); + } + + public PositionCoords(Mark mark, double x, double y) { + this.x = new XProperty(mark); + this.y = new YProperty(mark); + xRef = new ComponentRefXProperty(mark); + yRef = new ComponentRefYProperty(mark); + + this.x.set(x); + this.y.set(y); + } + + public double getX() { + return x.get(); + } + + public double getY() { + return y.get(); + } + + public XProperty getXProperty() { + return x; + } + + public YProperty getYProperty() { + return y; + } + + public ComponentRefXProperty getXRefProperty() { + return xRef; + } + + public ComponentRefYProperty getYRefProperty() { + return yRef; + } + + public RefX getXRef() { + return xRef.get(); + } + + public RefY getYRef() { + return yRef.get(); + } +} + diff --git a/src/fr/inria/structgraphics/graphics/ReferenceCoords.java b/src/fr/inria/structgraphics/graphics/ReferenceCoords.java new file mode 100644 index 0000000000000000000000000000000000000000..8f524941b7c1b76883eef82279a008a3418018b0 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/ReferenceCoords.java @@ -0,0 +1,142 @@ +package fr.inria.structgraphics.graphics; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.ContainerRefXProperty; +import fr.inria.structgraphics.types.ContainerRefYProperty; + +public class ReferenceCoords { + + public enum RefX { + Left, Right, Center + } + + public enum RefY { + Center, Top, Bottom + } + + public ContainerRefXProperty containerXRef = new ContainerRefXProperty(this); + public ContainerRefYProperty containerYRef = new ContainerRefYProperty(this); + + /* + public ComponentRefXProperty xRef = new ComponentRefXProperty(this); + public ComponentRefYProperty yRef = new ComponentRefYProperty(this); + */ + + public ReferenceCoords() {} + + + public ReferenceCoords(String contRefx, String contRefy) { + setXRef(contRefx); + setYRef(contRefy); + } + + /* + public ReferenceCoords(String contRefx, String compRefx, String contRefy, String compRefy) { + setXRef(contRefx, compRefx); + setYRef(contRefy, compRefy); + } + */ + + public ReferenceCoords(ReferenceCoords coords) { + containerXRef = coords.containerXRef; + containerYRef = coords.containerYRef; + + // xRef = coords.xRef; + // yRef = coords.yRef; + } + + public ReferenceCoords(RefX contRefX, RefY contRefY) { + containerXRef.set(contRefX); + containerYRef.set(contRefY); + } + + /* + public ReferenceCoords(RefX contRefX, RefX refX, RefY contRefY, RefY refY) { + containerXRef.set(contRefX); + xRef.set(refX); + containerYRef.set(contRefY); + yRef.set(refY); + } + */ + + /* + public void setXRef(String contRef, String componentRef) { + if(contRef.contains("left")) containerXRef.set(RefX.Left); + else if(contRef.contains("right")) containerXRef.set(RefX.Right); + else containerXRef.set(RefX.Center); + + if(componentRef.equals("left")) xRef.set(RefX.Left); + else if(componentRef.equals("right")) xRef.set(RefX.Right); + else xRef.set(RefX.Center); + } + + public void setYRef(String contRef, String componentRef) { + if(contRef.contains("top")) containerYRef.set(RefY.Top); + else if(contRef.contains("bottom")) containerYRef.set(RefY.Bottom); + else containerYRef.set(RefY.Center); + + if(componentRef.equals("top")) yRef.set(RefY.Top); + else if(componentRef.equals("bottom")) yRef.set(RefY.Bottom); + else yRef.set(RefY.Center); + }*/ + + public static RefX getXRef(String contRef) { + if(contRef.equalsIgnoreCase("left")) return RefX.Left; + else if(contRef.equalsIgnoreCase("right")) return RefX.Right; + else return RefX.Center; + } + + public static RefY getYRef(String contRef) { + if(contRef.equalsIgnoreCase("top")) return RefY.Top; + else if(contRef.equalsIgnoreCase("bottom")) return RefY.Bottom; + else return RefY.Center; + } + + public void setXRef(String contRef) { + if(contRef.contains("left")) containerXRef.set(RefX.Left); + else if(contRef.contains("right")) containerXRef.set(RefX.Right); + else containerXRef.set(RefX.Center); + } + + public void setYRef(String contRef) { + if(contRef.contains("top")) containerYRef.set(RefY.Top); + else if(contRef.contains("bottom")) containerYRef.set(RefY.Bottom); + else containerYRef.set(RefY.Center); + } + + public ContainerRefXProperty getContainerXRefProperty() { + return containerXRef; + } + + public ContainerRefYProperty getContainerYRefProperty() { + return containerYRef; + } + + public RefX getContainerXRef() { + return containerXRef.get(); + } + + public RefY getContainerYRef() { + return containerYRef.get(); + } + + /* + public ComponentRefXProperty getXRefProperty() { + return xRef; + } + + public ComponentRefYProperty getYRefProperty() { + return yRef; + } + + public RefX getXRef() { + return xRef.get(); + } + + public RefY getYRef() { + return yRef.get(); + } + */ +} + diff --git a/src/fr/inria/structgraphics/graphics/Shadow.java b/src/fr/inria/structgraphics/graphics/Shadow.java new file mode 100644 index 0000000000000000000000000000000000000000..535556562fba31730eea34817cf4c1fab6961fb0 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/Shadow.java @@ -0,0 +1,100 @@ +package fr.inria.structgraphics.graphics; + +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; +import javafx.scene.transform.Rotate; + +public class Shadow extends Group { + + private double dx, dy; + private SimpleMark mark; + + public Shadow(SimpleMark mark, double dx, double dy) { + this.dx = dx; + this.dy = dy; + this.mark = mark; + } + + public Shadow(SimpleMark mark, double dx, double dy, Shape shape) { + this(mark, dx, dy); + + Point2D r = mark.getRotationPoint(); + if(mark.angle.get() != 0) shape.getTransforms().add(new Rotate(mark.angle.get(), r.getX() + dx, r.getY() + dy)); + + getChildren().add(shape); + + highlight(false); + } + + public Shadow(SimpleMark mark, double dx, double dy, Group group) { + this(mark, dx, dy); + + Point2D r = mark.getRotationPoint(); + if(mark.angle.get() != 0) this.getTransforms().add(new Rotate(mark.angle.get(), r.getX() + dx, r.getY() + dy)); + + getChildren().addAll(group.getChildren()); + highlight(false); + } + + public void setStroke(Paint paint) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setStroke(paint); + } + } + + public void setStrokeWidth(double border) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setStrokeWidth(border); + } + } + + public void setFill(Paint paint) { + for(Node shape: getChildren()) { + if(shape instanceof Shape) ((Shape)shape).setFill(paint); + } + } + + private void highlight(boolean highlight) { + setStrokeWidth(highlight ? 1.8 : 0.7); + if(mark instanceof VisCollection) setFill(null); + else setFill(new Color(1, 1, 1, 0.5)); + setStroke(highlight ? Color.CORNFLOWERBLUE : Color.CORNFLOWERBLUE.brighter()); + } + + public boolean intersects(Shape trace) { + if(trace == null) { + highlight(false); + return false; + } + + for(Node shape: getChildren()) { + if(shape instanceof Shape) { + Shape intersection = Shape.intersect((Shape) shape, trace); + if(!(intersection instanceof Path) || !((Path)intersection).getElements().isEmpty()) { + highlight(true); + return true; + } + } + } + + highlight(false); + return false; + } + + public double getDeltaX() { + return dx; + } + + public double getDeltaY() { + return dy; + } + + public SimpleMark createMark() { + return mark.createCopy(dx, -dy); + } +} diff --git a/src/fr/inria/structgraphics/graphics/ShapeMark.java b/src/fr/inria/structgraphics/graphics/ShapeMark.java new file mode 100644 index 0000000000000000000000000000000000000000..50655c6cf82c4e5ebfae4e29d2cecf61a6b3f35d --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/ShapeMark.java @@ -0,0 +1,675 @@ +package fr.inria.structgraphics.graphics; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.graphics.controls.Control; +import fr.inria.structgraphics.graphics.controls.Control.ControlListener; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.utils.FlowConnectionBinding; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.LabelCollection; +import javafx.beans.property.Property; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Shape; + +public abstract class ShapeMark extends SimpleMark { + + protected Shape primitif; + public ShapeProperty shapeProperty; // TODO: turn this to a property... + + protected Circle flowMarker; + + public ShapeMark(Container parent, boolean toend, ShapeProperty.Type type, double w, double h){ + super(parent, toend, w, h); + + shapeProperty = new ShapeProperty(this, type); + primitif = createShape(); + + shape.add(primitif); + shape.addToGhost(xghost); + shape.addToGhost(yghost); + + // labels = new LabelCollection(this); + // group.getChildren().add(labels); + + initControls(); + } + + + @Override + public void stealProperties(SimpleMark mark) { + super.stealProperties(mark); + if(mark instanceof ShapeMark) { + shapeProperty.set(((ShapeMark)mark).shapeProperty.get()); + shapeProperty.getPublicProperty().set(((ShapeMark)mark).shapeProperty.getPublicProperty().get()); + } + ratiolock.set(mark.ratiolock.get()); + } + + public ShapeProperty getShapeProperty() { + return shapeProperty; + } + + public ShapeProperty.Type getShapeType() { + return shapeProperty.get(); + } + + public static ShapeMark createInstance(Container container, boolean toend, ShapeProperty.Type type, double width, double height) { + // New code that has a single class for all types of shapes!!! + if(type == ShapeProperty.Type.Text) return new TextualMark(container, toend, width, height); + return new GenericShapeMark(container, toend, type, width, height); + } + + protected abstract Shape createShape(); + protected abstract void refreshShape(); + + + @Override + public void dispose() { + super.dispose(); + + if(binding != null) { + binding.destroy(); + } + } + + @Override + protected void updateBasicShape() { + refreshShape(); + updateControls(); + updateGhost(); + labels.update(); + } + + + @Override + public void setHighlight(boolean highlight, boolean enableControls) { + this.highlighted = highlight; + shape.setHighlight(highlight); + + if(enableControls && (width() > 3 || height() > 3)) controls.setVisible(highlight); + else controls.setVisible(false); + + if(container instanceof VisBody) { + VisBody vgroup = (VisBody)container; + vgroup.highlighted(this, highlight); + } + } + + + @Override + public ShapeMark getShape(double x, double y) { + if(coords.xRef.get() == RefX.Left) x-= width.get()/2; + else if(coords.xRef.get() == RefX.Right) x+= width.get()/2; + + if(coords.yRef.get() == RefY.Bottom) y-= height.get()/2; + else if(coords.yRef.get() == RefY.Top) y+= height.get()/2; + + if(primitif.contains(x, y)) return this; + else return null; + } + + @Override + protected void initControls() { // TODO: + startX = new Control(); + startX.setControlListener(new ControlListener() { + private double w, x; + + @Override + public void pin() { + x = coords.x.get(); + w = width.get(); + } + + @Override + public void offsetX(double offset) { + x -= offset; + } + + @Override + public void offsetY(double offset) {} + + @Override + public void drag(Line lineTrace) { + double delta = lineTrace.getEndX() - lineTrace.getStartX(); + + switch(coords.getXRef()) { + case Left: + coords.x.set(x + delta); + width.updateValue(w - delta); + break; + case Right: + width.updateValue(w - delta); + break; + + default: + width.updateValue(w - 2*delta); + break; + } + } + }); + + endX = new Control(); + endX.setControlListener(new ControlListener() { + private double w, x; + + @Override + public void pin() { + x = coords.x.get(); + w = width.get(); + } + + @Override + public void offsetX(double offset) { + x -= offset; + } + + @Override + public void offsetY(double offset) {} + + @Override + public void drag(Line lineTrace) { + double delta = -lineTrace.getEndX() + lineTrace.getStartX(); + + switch(coords.getXRef()) { + case Left: + width.updateValue(w - delta); + break; + + case Right: + coords.x.set(x - delta); + width.updateValue(w - delta); + break; + + default: + width.updateValue(w - 2*delta); + break; + } + } + }); + + endY = new Control(); + endY.setControlListener(new ControlListener() { + private double h, y; + + @Override + public void pin() { + y = coords.y.get(); + h = height.get(); + } + + @Override + public void offsetX(double offset) {} + + @Override + public void offsetY(double offset) { + y+=offset; + } + + @Override + public void drag(Line lineTrace) { + double delta = lineTrace.getEndY() - lineTrace.getStartY(); + + switch(coords.getYRef()) { + case Bottom: + height.updateValue(h - delta); + break; + + case Top: + coords.y.set(y - delta); + height.updateValue(h - delta); + break; + + default: + height.updateValue(h - 2*delta); + break; + } + } + }); + + startY = new Control(); + startY.setControlListener(new ControlListener() { + private double h, y; + + @Override + public void pin() { + y = coords.y.get(); + h = height.get(); + } + + @Override + public void offsetX(double offset) {} + + @Override + public void offsetY(double offset) { + y+=offset; + } + + @Override + public void drag(Line lineTrace) { + double delta = -lineTrace.getEndY() + lineTrace.getStartY(); + + switch(coords.getYRef()) { + case Bottom: + coords.y.set(y + delta); + height.updateValue(h - delta); + break; + + case Top: + height.updateValue(h - delta); + break; + + default: + height.updateValue(h - 2*delta); + break; + } + } + }); + + // Code for corner controls + ////////////////////////// + topleft = new Control(); + topleft.setControlListener(new ControlListener() { + private double w, x, y, h; + + @Override + public void pin() { + x = coords.x.get(); + w = width.get(); + y = coords.y.get(); + h = height.get(); + } + + @Override + public void offsetX(double offset) { + x -= offset; + } + + @Override + public void offsetY(double offset) { + y+=offset; + } + + @Override + public void drag(Line lineTrace) { + double deltax = lineTrace.getEndX() - lineTrace.getStartX(); + double deltay = -lineTrace.getEndY() + lineTrace.getStartY(); + + switch(coords.getXRef()) { + case Left: + coords.x.set(x + deltax); + width.updateValue(w - deltax); + break; + case Right: + width.updateValue(w - deltax); + break; + default: + width.updateValue(w - 2*deltax); + break; + } + + switch(coords.getYRef()) { + case Bottom: + coords.y.set(y + deltay); + height.updateValue(h - deltay); + break; + + case Top: + height.updateValue(h - deltay); + break; + + default: + height.updateValue(h - 2*deltay); + break; + } + } + }); + + topright = new Control(); + topright.setControlListener(new ControlListener() { + private double w, x, y, h; + + @Override + public void pin() { + x = coords.x.get(); + w = width.get(); + y = coords.y.get(); + h = height.get(); + } + + @Override + public void offsetX(double offset) { + x -= offset; + } + + @Override + public void offsetY(double offset) { + y+=offset; + } + + @Override + public void drag(Line lineTrace) { + double deltax = -lineTrace.getEndX() + lineTrace.getStartX(); + double deltay = -lineTrace.getEndY() + lineTrace.getStartY(); + + switch(coords.getXRef()) { + case Left: + width.updateValue(w - deltax); + break; + + case Right: + coords.x.set(x - deltax); + width.updateValue(w - deltax); + break; + + default: + width.updateValue(w - 2*deltax); + break; + } + + switch(coords.getYRef()) { + case Bottom: + coords.y.set(y + deltay); + height.updateValue(h - deltay); + break; + + case Top: + height.updateValue(h - deltay); + break; + + default: + height.updateValue(h - 2*deltay); + break; + } + } + }); + + bottomleft = new Control(); + bottomleft.setControlListener(new ControlListener() { + private double w, x, y, h; + + @Override + public void pin() { + x = coords.x.get(); + w = width.get(); + y = coords.y.get(); + h = height.get(); + } + + @Override + public void offsetX(double offset) { + x -= offset; + } + + @Override + public void offsetY(double offset) { + y+=offset; + } + + @Override + public void drag(Line lineTrace) { + double deltax = lineTrace.getEndX() - lineTrace.getStartX(); + double deltay = lineTrace.getEndY() - lineTrace.getStartY(); + + switch(coords.getXRef()) { + case Left: + coords.x.set(x + deltax); + width.updateValue(w - deltax); + break; + case Right: + width.updateValue(w - deltax); + break; + default: + width.updateValue(w - 2*deltax); + break; + } + + switch(coords.getYRef()) { + case Bottom: + height.updateValue(h - deltay); + break; + + case Top: + coords.y.set(y - deltay); + height.updateValue(h - deltay); + break; + + default: + height.updateValue(h - 2*deltay); + break; + } + } + }); + + bottomright = new Control(); + bottomright.setControlListener(new ControlListener() { + private double w, x, y, h; + + @Override + public void pin() { + x = coords.x.get(); + w = width.get(); + y = coords.y.get(); + h = height.get(); + } + + @Override + public void offsetX(double offset) { + x -= offset; + } + + @Override + public void offsetY(double offset) { + y+=offset; + } + + @Override + public void drag(Line lineTrace) { + double deltax = -lineTrace.getEndX() + lineTrace.getStartX(); + double deltay = lineTrace.getEndY() - lineTrace.getStartY(); + + switch(coords.getXRef()) { + case Left: + width.updateValue(w - deltax); + break; + + case Right: + coords.x.set(x - deltax); + width.updateValue(w - deltax); + break; + + default: + width.updateValue(w - 2*deltax); + break; + } + + switch(coords.getYRef()) { + case Bottom: + height.updateValue(h - deltay); + break; + + case Top: + coords.y.set(y - deltay); + height.updateValue(h - deltay); + break; + + default: + height.updateValue(h - 2*deltay); + break; + } + } + }); + + ////////////////////////// + controls.getChildren().addAll(startX, endX, endY, startY, bottomleft, topright, topleft, bottomright); + } + + protected void updateControls() { // TODO: Replace by bindings? + startX.moveTo(-width.get()/2, 0); + endX.moveTo(width.get()/2, 0); + + endY.moveTo(0, -height.get()/2); + startY.moveTo(0, height.get()/2); + + topleft.moveTo(-width.get()/2, height.get()/2); + topright.moveTo(width.get()/2, height.get()/2); + + bottomleft.moveTo(-width.get()/2, -height.get()/2); + bottomright.moveTo(width.get()/2, -height.get()/2); + } + + @Override + public SimpleMark createCopy(Container container, double dx, double dy) { + double x = coords.getX() + dx; + double y = coords.getY() + dy; + + ShapeMark shapeMark = ShapeMark.createInstance(container, container.addToEnd(x, y), shapeProperty.get(), width(), height()); + + shapeMark.stealProperties(this); + shapeMark.coords.x.set(x); + shapeMark.coords.y.set(y); + + return shapeMark; + } + + + @Override + public boolean monoselect(MarkSelection selectedMarks, Circle trace) { + if(trace == null) return false; + if(width() > 3 || height() > 3) return super.monoselect(selectedMarks, trace); + boolean intersects = new Circle(trace.getCenterX(), getRootHeight() - trace.getCenterY(), 10).contains(getGlobalX(), getGlobalY()); + + if(intersects) { + pinX = coords.getX(); + pinY = coords.getY(); + selectedMarks.add(this); + } + + return intersects; + } + + + public void setDefaults() { + border.set(0.5); + strokePaint.set(Paint.valueOf("#505080")); + coords.xRef.set(RefX.Center); + } + + /* + @Override + public void showLabel(DataVariable variable, Property property) { + if(isLabelShown(variable) == variable.nodeShownProperty.get()) return; + labels.showVariable(variable, property); + } + + public boolean isLabelShown(DataVariable variable) { + return labels.isLabelShown(variable); + } + */ + + /* + * The following is a set of methods for dealing with flow connections (flow diagrams) + * + */ + protected FlowConnectionBinding binding; + + public FlowConnectionBinding getFlowConnectionsBinding() { + return binding; + } + + public void addInwardConnection(FlowConnection connection) { + if(binding == null) binding = new FlowConnectionBinding(this); + binding.addInwardConnection(connection); + } + + public void addOutwardConnection(FlowConnection connection) { + if(binding == null) binding = new FlowConnectionBinding(this); + binding.addOutwardConnection(connection); + } + + public void removeInwardConnection(FlowConnection connection) { + binding.removeInwardConnection(connection); + } + + public void removeOutwardConnection(FlowConnection connection) { + binding.removeOutwardConnection(connection); + } + + public boolean hasInwardConnections() { + return binding != null && !binding.getInwardConnections().isEmpty(); + } + + public boolean hasOutwardConnections() { + return binding != null && !binding.getOutwardConnections().isEmpty(); + } + + public void lockListeners(boolean lock) { + if(binding != null) binding.lockListeners(lock); + } + + //////////////// + + public void showOrigin(boolean show) { + if(show && flowMarker == null) { + flowMarker = new Circle(5); + flowMarker.setStroke(Color.CORNFLOWERBLUE); + flowMarker.setFill(null); + flowMarker.centerXProperty().set(Math.abs(width.get())/2); + flowMarker.centerYProperty().set(0); + group.getChildren().add(flowMarker); + } else if(!show && flowMarker != null){ + group.getChildren().remove(flowMarker); + flowMarker = null; + } + } + + public void showDestination(boolean show) { + if(show && flowMarker == null) { + flowMarker = new Circle(8); + flowMarker.setStroke(Color.CORNFLOWERBLUE); + flowMarker.setFill(null); + flowMarker.centerXProperty().set(-Math.abs(width.get())/2); + flowMarker.centerYProperty().set(0); + group.getChildren().add(flowMarker); + } else if(!show && flowMarker != null){ + group.getChildren().remove(flowMarker); + flowMarker = null; + } + } + + /////////////////// + private double originYflag = 0, destinationYflag = 0; + void increaseOriginFlag(double dy) { + originYflag += dy; + } + + void increaseDestinationFlag(double dy) { + destinationYflag += dy; + } + + void resetConnectionFlags() { + originYflag = 0; + destinationYflag = 0; + } + + double getOriginFlag() { + return originYflag; + } + + double getDestinationFlag() { + return destinationYflag; + } + + @Override + public Property getProperty(PropertyName name) { + if(name.isShape()) return shapeProperty; + else return super.getProperty(name); + } + +} diff --git a/src/fr/inria/structgraphics/graphics/SimpleMark.java b/src/fr/inria/structgraphics/graphics/SimpleMark.java new file mode 100644 index 0000000000000000000000000000000000000000..b76a25006fdaeed2c47828b4a27091779b4be7e3 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/SimpleMark.java @@ -0,0 +1,200 @@ +package fr.inria.structgraphics.graphics; + +import java.util.List; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.graphics.controls.Control; +import fr.inria.structgraphics.types.Shareable; +import javafx.beans.property.Property; +import javafx.geometry.Point2D; +import javafx.scene.paint.Color; +import javafx.scene.shape.Line; +import javafx.scene.shape.Rectangle; + +public abstract class SimpleMark extends Mark { + + protected Arrow xghost = new Arrow(), yghost = new Arrow(); + protected MultiplyFeedForward mfeedforward = null; + + // These are less important properties to be somehow linked to the main properties + protected Control startX, endX, startY, endY; + + // Additional controls for the corners + protected Control topleft, topright, bottomleft, bottomright; + + public SimpleMark(Container parent, boolean toend, double width, double height){ + super(parent, toend, width, height); + } + + @Override + public void initProperties() { + super.initProperties(); + + addProperty(this.width); + addProperty(this.height); + + addProperty(border); + addProperty(angle); + + addProperty(paint); + addProperty(strokePaint); + } + + + public void stealProperties(SimpleMark mark) { + border.set(mark.border.get()); + border.getPublicProperty().set(mark.border.getPublicProperty().get()); + + if(mark.paint != null) { + paint.set(mark.paint.get()); + paint.getPublicProperty().set(mark.paint.getPublicProperty().get()); + } else paint = null; + + + angle.set(mark.angle.get()); + angle.getPublicProperty().set(mark.angle.getPublicProperty().get()); + + if(mark.strokePaint != null) { + strokePaint.set(mark.strokePaint.get()); + strokePaint.getPublicProperty().set(mark.strokePaint.getPublicProperty().get()); + } else mark.strokePaint = null; + + coords = new PositionCoords(this, mark.coords.getX(), mark.coords.getY()); + coords.x.getPublicProperty().set(mark.coords.x.getPublicProperty().get()); + coords.y.getPublicProperty().set(mark.coords.y.getPublicProperty().get()); + + coords.xRef.set(mark.coords.getXRef()); + coords.xRef.getPublicProperty().set(mark.coords.xRef.getPublicProperty().get()); + + coords.yRef.set(mark.coords.getYRef()); + coords.yRef.getPublicProperty().set(mark.coords.yRef.getPublicProperty().get()); + + width.getPublicProperty().set(mark.width.getPublicProperty().get()); + height.getPublicProperty().set(mark.height.getPublicProperty().get()); + } + + public double width() { + return width.get(); + } + + public double height() { + return height.get(); + } + + public Arrow getXGhost() { + return xghost; + } + + public Arrow getYGhost() { + return yghost; + } + + // This is some code for creating and handling the ghost + public void updateGhost() { + switch(coords.getXRef()) { + case Left: + xghost.setEnds(-width.get()/2, -width.get()/2, height.get()/2, -height.get()/2); + break; + + case Right: + xghost.setEnds(width.get()/2, width.get()/2, height.get()/2, -height.get()/2); + break; + + default: + xghost.setEnds(0, 0, height.get()/2, -height.get()/2); + break; + } + + switch(coords.getYRef()) { + case Top: + yghost.setEnds(-width.get()/2, width.get()/2, -height.get()/2, -height.get()/2); + break; + + case Bottom: + yghost.setEnds(-width.get()/2, width.get()/2, height.get()/2, height.get()/2); + break; + + default: + yghost.setEnds(-width.get()/2, width.get()/2, 0, 0); + break; + } + + } + + @Override + public void updateShape() { + double cx = 0, cy = 0; + + // X axis + if(coords.getXRef() == RefX.Left) cx = coords.getX() + width.get()/2; + else if(coords.getXRef() == RefX.Right) cx = coords.getX() - width.get()/2; + else cx = coords.getX(); + + // Y axis + if(coords.getYRef() == RefY.Bottom) cy = coords.getY() + height.get()/2; + else if(coords.getYRef() == RefY.Top) cy = coords.getY() - height.get()/2; + else cy = coords.getY(); + + // TODO: I also need to derive the angle in case the parent is a curve!!! + + super.updateShape(cx, cy); + + } + + @Override + public Point2D getReferencePoint(double cx, double cy) { + double x, y; + + if(refCoords.containerXRef.get() == RefX.Left) x = cx - width.get()/2; + else if(refCoords.containerXRef.get() == RefX.Right) x = width.get()/2 + cx; + else x = cx; + + if(refCoords.containerYRef.get() == RefY.Top) y = -cy - height.get()/2; + else if(refCoords.containerYRef.get() == RefY.Bottom) y = height.get()/2 - cy; + else y = -cy; + + return new Point2D(x, y); + } + + + public void setFeedForward(boolean activate) { + VisFrame frame = (VisFrame)getRoot(); + + if(activate) { + mfeedforward = new MultiplyFeedForward(this); + + Point2D ref = getRotationPoint(); + mfeedforward.translateXProperty().set(mfeedforward.translateXProperty().get() + getGlobalX() - ref.getX()); + mfeedforward.translateYProperty().set(mfeedforward.translateYProperty().get() + getRootHeight() - getGlobalY() - ref.getY()); + + getRoot().getGroup().getChildren().add(frame.createBedSheet()); + getRoot().getGroup().getChildren().add(mfeedforward); + } + else { + getRoot().getGroup().getChildren().remove(mfeedforward); + getRoot().getGroup().getChildren().remove(frame.getBedSheet()); + + mfeedforward = null; + } + } + + public Shadow getShadow(double dx, double dy, double delta) { + return new Shadow(this, dx*delta, dy*delta); // TODO: ??? + } + + + public SimpleMark createCopy(double dx, double dy) { // TODO: Turn to abstract? + return createCopy(this.container, dx, dy); + } + + public SimpleMark createCopy(Container container, double dx, double dy) { + return null; + } + + public List<Shadow> selectCopies(Line lineTrace) { + return mfeedforward.select(lineTrace); + } + + +} diff --git a/src/fr/inria/structgraphics/graphics/TextualMark.java b/src/fr/inria/structgraphics/graphics/TextualMark.java new file mode 100644 index 0000000000000000000000000000000000000000..6b1d11da04a0dbd3fdabbf67d6dd10fdb4bad6f1 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/TextualMark.java @@ -0,0 +1,211 @@ +package fr.inria.structgraphics.graphics; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.FontSizeProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.TextProperty; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; + +public class TextualMark extends ShapeMark { + + public TextProperty textProperty = new TextProperty(this, "your text"); + public FontSizeProperty fontSize = new FontSizeProperty(this, 14); + + protected TextualMark(Container parent, boolean toend, double w, double h){ + super(parent, toend, ShapeProperty.Type.Text, w, h); + } + + @Override + public void initProperties() { + super.initProperties(); + removeProperty(border); + removeProperty(strokePaint); + + switch(coords.xRef.get()) { + case Center: ((Text)primitif).setTextAlignment(TextAlignment.CENTER); + break; + case Left: ((Text)primitif).setTextAlignment(TextAlignment.LEFT); + break; + case Right: ((Text)primitif).setTextAlignment(TextAlignment.RIGHT); + break; + default: + break; + } + + coords.xRef.addListener(new ChangeListener<RefX>() { + @Override + public void changed(ObservableValue<? extends RefX> observable, RefX oldValue, RefX newValue) { + switch(newValue) { + case Center: ((Text)primitif).setTextAlignment(TextAlignment.CENTER); + break; + case Left: ((Text)primitif).setTextAlignment(TextAlignment.LEFT); + break; + case Right: ((Text)primitif).setTextAlignment(TextAlignment.RIGHT); + break; + default: + break; + } + } + }); + + + //Text text = new Text(-Math.abs(width.get())/2, Math.abs(height.get())/2, "title"); + coords.yRef.addListener(new ChangeListener<RefY>() { + @Override + public void changed(ObservableValue<? extends RefY> observable, RefY oldValue, RefY newValue) { + switch(newValue) { + case Center: ((Text)primitif).setY(0); + break; + case Bottom: ((Text)primitif).setY(height.get()/2); + break; + case Top: ((Text)primitif).setY(-height.get()/2); + break; + default: + break; + } + } + }); + switch(coords.yRef.get()) { + case Center: ((Text)primitif).setY(0); + break; + case Bottom: ((Text)primitif).setY(height.get()/2); + break; + case Top: ((Text)primitif).setY(-height.get()/2); + break; + default: + break; + } + + ((Text)primitif).setFont(new Font(fontSize.get())); + fontSize.addListener( + new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + ((Text)primitif).setFont(new Font(fontSize.get())); + } + }); + + ((Text)primitif).textProperty().bindBidirectional(textProperty); + + addProperty(textProperty); + addProperty(fontSize); + } + + @Override + public void stealProperties(SimpleMark mark) { + super.stealProperties(mark); + if(mark instanceof TextualMark) { + textProperty.set(((TextualMark)mark).textProperty.get()); + textProperty.getPublicProperty().set(((TextualMark)mark).textProperty.getPublicProperty().get()); + + fontSize.set(((TextualMark)mark).fontSize.get()); + fontSize.getPublicProperty().set(((TextualMark)mark).fontSize.getPublicProperty().get()); + } + } + + @Override + protected Shape createShape() { + Text text = new Text(-width.get()/2, height.get()/2, "title"); + + text.wrappingWidthProperty().bind(width); + //width.set(text.getLayoutBounds().getWidth()); + + text.onMouseClickedProperty().set(new EventHandler<MouseEvent>() { + TextField field = null; + @Override + public void handle(MouseEvent event) { + if(event.getClickCount() > 1 && field == null) { + field = new TextField(text.getText()); + field.translateXProperty().set(getGlobalX()); + field.translateYProperty().set(getRootHeight() - getGlobalY()); + getRoot().addTextField(field, true); + field.setOnKeyPressed(new EventHandler<KeyEvent>() { + public void handle(KeyEvent ke) { + if (ke.getCode().equals(KeyCode.ENTER)) { + text.setText(field.getText()); + getRoot().addTextField(field, false); + getRoot().update(); + field = null; + } else { + + } + } + }); + + + } else if(field != null) { + getRoot().addTextField(field, false); + getRoot().update(); + field = null; + } + } + }); + + return text; + } + + @Override + protected void refreshShape() { + Text text = (Text)primitif; + text.xProperty().set(-width.get()/2); + + switch(coords.yRef.get()) { + case Center: ((Text)primitif).setY(0); + break; + case Bottom: ((Text)primitif).setY( height.get()/2); + break; + case Top: ((Text)primitif).setY(-height.get()/2); + break; + default: + break; + } + } + + + @Override + public String getName() { + return shapeProperty.get().name(); + } + + @Override + public String getType() { + return shapeProperty.get().name(); } + + + @Override + public Shadow getShadow(double dx, double dy, double delta) { + dx *= delta; + dy *= delta; + + return new Shadow(this, dx, dy, new Rectangle(dx-Math.abs(width.get())/2, dy-Math.abs(height.get())/2, + Math.abs(width.get()), Math.abs(height.get()))); // TODO: ??? + } + + @Override + public void setDefaults() { + border.set(0); + paint.set(Color.DIMGREY); + } + + @Override + public Property getProperty(PropertyName name) { + if(name.isFontSize()) return fontSize; + else if(name.isText()) return textProperty; + else return super.getProperty(name); + } +} diff --git a/src/fr/inria/structgraphics/graphics/VisBody.java b/src/fr/inria/structgraphics/graphics/VisBody.java new file mode 100644 index 0000000000000000000000000000000000000000..83cb9233047dab4cb6294c65ebf12cc934624d2c --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/VisBody.java @@ -0,0 +1,1079 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.AlignmentXProperty; +import fr.inria.structgraphics.types.AlignmentYProperty; +import fr.inria.structgraphics.types.DeltaXProperty; +import fr.inria.structgraphics.types.DeltaYProperty; +import fr.inria.structgraphics.types.DistributionProperty; +import fr.inria.structgraphics.types.DistributionXProperty; +import fr.inria.structgraphics.types.DistributionYProperty; +import fr.inria.structgraphics.types.IdentifierProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.XMinProperty; +import fr.inria.structgraphics.types.YMinProperty; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.types.ShapeProperty.Type; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.tools.SelectFilter; +import fr.inria.structgraphics.ui.utils.SortedMarkSet; +import fr.inria.structgraphics.ui.viscanvas.groupings.DefaultSharingStrategy; +import fr.inria.structgraphics.ui.viscanvas.groupings.PropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.BodyInteractor; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.ConstraintController; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.ConstraintXController; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.ConstraintYController; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Point2D; +import javafx.scene.Group; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Shape; + +public abstract class VisBody extends SimpleMark implements InvalidationListener { + + protected PropertyStructure childPropertyStructure; + + public AlignmentXProperty alignXProperty = new AlignmentXProperty(this); + public AlignmentYProperty alignYProperty = new AlignmentYProperty(this); + + public DistributionXProperty constraintXProperty = new DistributionXProperty(this); + public DistributionYProperty constraintYProperty = new DistributionYProperty(this); + + public DeltaYProperty distanceYProperty = new DeltaYProperty(this); + public DeltaXProperty distanceXProperty = new DeltaXProperty(this); + + // TODO: add them!!!!! + public YMinProperty minYProperty = new YMinProperty(this); + public XMinProperty minXProperty = new XMinProperty(this); + + // These are additional properties used for interaction and visualization purposes + protected BodyInteractor interactor; + protected ConstraintController controllerX, controllerY; + + protected Group shapes = new Group(); + + protected boolean toUpdateLayout = true; + + public VisBody(Container parent, boolean toend) { + super(parent, toend, 0, 0); + + group.getChildren().add(shapes); + + interactor = createInteractor(); + group.getChildren().add(interactor); + + addExtraComponents(); + + controllerX = new ConstraintXController(this); + group.getChildren().add(controllerX); + + controllerY = new ConstraintYController(this); + group.getChildren().add(controllerY); + + + alignXProperty.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if(components.isEmpty()) return; + + if(alignXProperty.get() == YSticky.Yes) { + if(constraintXProperty.get() == Constraint.None) { + childPropertyStructure.moveToCommon(new PropertyName(components.get(0).coords.x.getName())); + + for(Mark mark: components) { // TODO: needed all this? + mark.coords.x.set(0); + mark.coords.x.getActiveProperty().set(false); + } + //constraintXProperty.setValue(Constraint.None); + //constraintXProperty.getActiveProperty().set(false); + } else { + // TODO: ???? + components.get(0).coords.x.set(0); + HierarchyPos pos = new HierarchyPos(components.get(0)); + getRootVirtualGroup().updateTreeLayout(pos); + // + } + } else { + for(Mark mark: components) { + mark.coords.x.getActiveProperty().set(true); + } + } + + invalidateInteractor(); + } + }); + + alignYProperty.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if(components.isEmpty()) return; + + if(alignYProperty.get() == XSticky.Yes) { + if(constraintYProperty.get() == Constraint.None) { + childPropertyStructure.moveToCommon(new PropertyName(components.get(0).coords.y.getName())); + + for(Mark mark: components) { + mark.coords.y.set(0); + mark.coords.y.getActiveProperty().set(false); + } + // constraintYProperty.setValue(Constraint.None); + // constraintYProperty.getActiveProperty().set(false); + } else { // TODO:??? + components.get(0).coords.y.set(0); + HierarchyPos pos = new HierarchyPos(components.get(0)); + getRootVirtualGroup().updateTreeLayout(pos); + } + } else { + for(Mark mark: components) { + mark.coords.y.getActiveProperty().set(true); + } + } + + invalidateInteractor(); + } + }); + } + + protected abstract BodyInteractor createInteractor(); + + protected void addExtraComponents() { + + } + + protected void updateExtraComponents() { + + } + + //////////////////////////////////////////////////////////// + public boolean isOrderConstrained() { + if(constraintXProperty.get() == DistributionProperty.Constraint.None + && constraintYProperty.get() == DistributionProperty.Constraint.None) return false; + else return true; + } + + public DistributionXProperty getConstraintXProperty() { + return constraintXProperty; + } + + public DistributionYProperty getConstraintYProperty() { + return constraintYProperty; + } + + public AlignmentXProperty getAlignXProperty() { + return alignXProperty; + } + + public AlignmentYProperty getAlignYProperty() { + return alignYProperty; + } + + + public void setLevel(int level) { + this.level = level; + id = new IdentifierProperty(this, PropertyName.getPrefix(level) + "id"); + } + + @Override + protected void updateBasicShape() { + labels.update(); + } + + public BodyInteractor getInteractor() { + return interactor; + } + + @Override + public void updateShape() { + super.updateShape(coords.getX(), coords.getY()); + + } + + @Override + public Point2D getReferencePoint(double cx, double cy) { + return new Point2D(cx, -cy); + } + + @Override + public void initProperties() { + if(coords != null) { + addProperty(coords.getXProperty()); + addProperty(coords.getYProperty()); + } + + //if(this instanceof VisCollection) { + addProperty(alignXProperty); + addProperty(alignYProperty); + //} + + addProperty(constraintXProperty); + addProperty(constraintYProperty); + + constraintXProperty.addListener(new ChangeListener<Constraint>() { + @Override + public void changed(ObservableValue observable, Constraint oldValue, Constraint newValue) { + // TODO Auto-generated method stub + if(newValue == Constraint.None + || newValue == Constraint.Distance) { // TODO: CORRECT? + distanceXProperty.getActiveProperty().set(false); + alignXProperty.set(YSticky.No); + } + else { + distanceXProperty.getActiveProperty().set(true); + if(alignXProperty.get() == YSticky.Yes) { + + for(Mark mark: components) { + mark.coords.x.getActiveProperty().set(true); + } + childPropertyStructure.moveToVariable(new PropertyName(components.get(0).coords.x.getName())); + distanceXProperty.set(10); + } else childPropertyStructure.moveToVariable(new PropertyName(components.get(0).coords.x.getName())); + + if(container instanceof VisBody) { + ((VisBody) container).childPropertyStructure.fixXSharing(); + } + } + } + }); + + constraintYProperty.addListener(new ChangeListener<Constraint>() { + @Override + public void changed(ObservableValue observable, Constraint oldValue, Constraint newValue) { + if(newValue == Constraint.None + || newValue == Constraint.Distance) { // TODO: Correct? + distanceYProperty.getActiveProperty().set(false); + alignYProperty.set(XSticky.No); + } + else { + distanceYProperty.getActiveProperty().set(true); + if(alignYProperty.get() == XSticky.Yes) { + for(Mark mark: components) { + mark.coords.y.getActiveProperty().set(true); + } + childPropertyStructure.moveToVariable(new PropertyName(components.get(0).coords.y.getName())); + distanceYProperty.set(10); + } else { + childPropertyStructure.moveToVariable(new PropertyName(components.get(0).coords.y.getName())); + } + + + if(container instanceof VisBody) { + ((VisBody) container).childPropertyStructure.fixYSharing(); + } + } + } + }); + + addProperty(angle); + + // TODO: To remove them from the UI - make them shared??? + addProperty(distanceXProperty); + addProperty(distanceYProperty); + + /* + // Update order when necessary! + coords.x.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + List<Mark> siblings = container.getComponents(); + + if(container instanceof VisCollection && ((VisCollection) container).constraintXProperty.get() == Constraint.None) { + ((VisCollection)container).reorderChildren(true); + } + } + });*/ + } + + public void initVisibility() { + distanceXProperty.getActiveProperty().set(false); + distanceYProperty.getActiveProperty().set(false); + } + + + public void updateInteractor() { + double left = Double.MAX_VALUE, right = -Double.MAX_VALUE, top = Double.MAX_VALUE, bottom = -Double.MAX_VALUE; +// double minX = Double.MAX_VALUE, maxX = -Double.MAX_VALUE, maxY = Double.MAX_VALUE, minY = -Double.MAX_VALUE; + + for(Mark mark:components) { + left = Math.min(left, mark.getLeft()); + right = Math.max(right, mark.getRight()); + top = Math.min(top, mark.getTop()); + bottom = Math.max(bottom, mark.getBottom()); + } + + YSticky alignX = alignXProperty.get(); + + if(alignX == YSticky.No) { + interactor.left.set(Math.min(0, left)); + interactor.right.set(Math.max(0, right)); + } else { + interactor.left.set(left); + interactor.right.set(right); + } + + XSticky alignY = alignYProperty.get(); + if(alignY == XSticky.No) { + interactor.top.set(Math.min(0, top)); + interactor.bottom.set(Math.max(0, bottom)); + } else { + interactor.top.set(top); + interactor.bottom.set(bottom); + } + + interactor.refresh(); + } + + /* + * Re-orders all children based on + */ + public void reorderChildren(boolean xaxis) { + if(xaxis && (alignXProperty.get() == YSticky.Yes || childPropertyStructure.isXShared() + || constraintYProperty.get() != Constraint.None) + || !xaxis && (alignYProperty.get() == XSticky.Yes || childPropertyStructure.isYShared() + || constraintXProperty.get() != Constraint.None)) return; + + boolean ordered = true; + + if(xaxis) { + for(int i = 1; i < components.size() && ordered; ++ i) { + if(components.get(i).coords.getX() < components.get(i - 1).coords.getX()) + ordered = false; + } + } else { + // TODO: CHECK + for(int i = 1; i < components.size() && ordered; ++ i) { + if(components.get(i).coords.getY() < components.get(i - 1).coords.getY()) + ordered = false; + } + } + if(ordered) return; + + //TODO: This is incomplete. i need to take care of the properties structure and update the inspector? + SortedMarkSet sorted = new SortedMarkSet(xaxis); + sorted.addAll(components); + + for(Mark mark: components) { + shapes.getChildren().remove(mark.group); + } + components.clear(); + + for(Mark mark: sorted) { + components.add(mark); + } + + for(Mark mark: sorted) { + shapes.getChildren().add(mark.group); + } + + updateReordering(sorted); + + } + + protected void updateReordering(Collection<Mark> marks) { + refreshID(); + + PropertyStructure structure = PropertyStructure.create(marks, childPropertyStructure); + childPropertyStructure.destroyAllBindings(); + setPropertyStructure(structure); + + if(container instanceof VisBody) ((VisBody)container).updateReordering(); + else { + VisFrame frame = (VisFrame) getRoot(); + frame.getInspector().updateOrder(); + } + } + + public void addToPropertyStructure(Mark mark) {// TODO... + childPropertyStructure.rebuildBindings(); + + if(container instanceof VisBody) + ((VisBody)container).addToPropertyStructure(mark); + } + + + @Override + public boolean addToEnd(double x, double y) { + if(constraintXProperty.get() != Constraint.None && x < components.get(0).coords.getX() + || constraintYProperty.get() != Constraint.None && y < components.get(0).coords.getY()) { + return false; + } + else return true; + } + + @Override + public void addMark(Mark mark, boolean toend) { + mark.container = this; + + if(toend) { + components.add(mark); + shapes.getChildren().add(mark.group); + } + else { + components.add(0, mark); + shapes.getChildren().add(0, mark.group); + } + + if(mark instanceof VisBody && ((VisBody)mark).getInteractor() != null) + ((VisBody)mark).getInteractor().redraw(); + } + + public void addToGroup(Mark mark) { // TODO.... + if(mark instanceof VisBody) childPropertyStructure.addMark(mark, new DefaultSharingStrategy()); + else childPropertyStructure.addMark(mark); + + if(container instanceof VisBody) + ((VisBody)container).addToPropertyStructure(mark); + + // TODO: These listener should act after the property sharings are activated + // Listen to changes of the children nodes + mark.coords.x.addListener(this); + mark.coords.y.addListener(this); + mark.width.addListener(this); + mark.height.addListener(this); + + updateInteractor(); + } + + + /* + public void removeFromPropertyStructure(Mark mark) { + childPropertyStructure.removeNestedMark(mark); + }*/ + + public void attachCopy(Mark mark/*, boolean sharedX, boolean sharedY*/) { + + // Listen to changes of the children nodes + mark.coords.x.addListener(this); + mark.coords.y.addListener(this); + mark.width.addListener(this); + mark.height.addListener(this); + + //mark.coords.xRef.addListener(this); + //mark.coords.yRef.addListener(this); + + updateInteractor(); + } + + public void attach(Mark mark, boolean sharedX, boolean sharedY) { + Container container = mark.getContainer(); + container.removeMark(mark); + + double xpos, ypos; + + RefX xref = mark.coords.xRef.get(); + RefY yref = mark.coords.yRef.get(); + + if(mark instanceof VisBody) { + xpos = mark.getCoords().getX(); + ypos = mark.getCoords().getY(); + } + else { + if(xref == RefX.Left) xpos = mark.left(); + else if (xref == RefX.Right) xpos = mark.right(); + else xpos = mark.centerX(); + + if(yref == RefY.Top) ypos = mark.top(); + else if (yref == RefY.Bottom) ypos = mark.bottom(); + else ypos = mark.centerY(); + } + + + mark.setPositionCoords(sharedX ? 0 : xpos - coords.getX(), + sharedY ? 0 : ypos - coords.getY()); + + addMark(mark, true); + + // Listen to changes of the children nodes + mark.coords.x.addListener(this); + mark.coords.y.addListener(this); + mark.width.addListener(this); + mark.height.addListener(this); + } + + public void refresh() { + refreshID(); + updateInteractor(); + } + + @Override + public void removeMark(Mark mark) { + if(components.contains(mark)) components.remove(mark); + if(childPropertyStructure != null) + childPropertyStructure.removeMark(mark); + + refreshID(); + + //stickToYAxis(); + //stickToXAxis(); + //updateInteractor(); + //updateTreeLayout(components.get(0)); + + if(container instanceof VisBody) { + ((VisBody)container).removeMark(mark); + } + + refreshID(); + + if(!getComponents().isEmpty()) invalidateControllerAt(0, stickToXAxis(), stickToYAxis()); + } + + + @Override + public void select(MarkSelection selectedMarks, Shape trace, SelectFilter filter) { + if(!filter.accept(selectedMarks.getMarks(), this)) return; + + if(interactor.intersects(trace)) { + pinX = coords.getX(); + pinY = coords.getY(); + selectedMarks.add(this); + + return; + } + else super.select(selectedMarks, trace, filter); + } + + + @Override + public boolean monoselect(MarkSelection selectedMarks, Circle trace) { // To review how to prioritize its selection or not... + for(int i = components.size() - 1; i >=0; --i) { + if(((Mark)components.get(i)).controlselect(selectedMarks, trace)) return true; + } + + + // TODO : This is just to improve picking of lines. It's a last-moment hack!!! It need replacement!!!! + for(int i = components.size() - 1; i >=0; --i) { + Mark mark = (Mark)components.get(i); + if(mark instanceof GenericShapeMark && ((GenericShapeMark)mark).shapeProperty.get() == Type.Line && mark.monoselect(selectedMarks, trace)) + return true; + } + + + if(interactor.intersects(trace)) { + pin(); + selectedMarks.add(this); + return true; + } + + for(int i = components.size() - 1; i >=0; --i) { + if(((Mark)components.get(i)).monoselect(selectedMarks, trace)) return true; + } + + return false; + } + + @Override + public boolean controlselect(MarkSelection selectedMarks, Circle trace) { + for(int i = components.size() - 1; i >=0; --i) { + if(((Mark)components.get(i)).controlselect(selectedMarks, trace)) { + return true; + } + } + + if(controllerX.intersects(trace) || controllerY.intersects(trace)) { + Mark mark = getHighlightedChild(); + + if(mark!=null) { + selectedMarks.setDragAccept(false); + selectedMarks.add(mark); + return true; + } + } + + if(interactor.intersectsCenter(trace)) { + pinX = coords.getX(); + pinY = coords.getY(); + selectedMarks.add(this); + return true; + } + + return false; + } + + + @Override + public void setHighlight(boolean highlight, boolean enableControls) { + this.highlighted = highlight; + + interactor.setHighlight(highlight); + + if(container instanceof VisBody) { + VisBody vgroup = (VisBody)container; + vgroup.highlighted(this, highlight); + } + } + + @Override + public String getType() { + return "Group"; + } + + public void dispose() { + container.removeMark(this); + group.getChildren().clear(); // TODO: Is this OK? + } + + + protected void invalidateInteractor() { + updateBasicShape(); + + // TODO: I commented that. Is there any reason to exist? +// updateInteractor(); +// updateXAxisOrigin(); +// updateYAxisOrigin(); + updateInteractor(); + + updateExtraComponents(); + + /* + if(container instanceof VisBody) { + ((VisBody)container).invalidateController(this); + ((VisBody)container).invalidateInteractor(); + }*/ + } + + protected void invalidateConstraints(Mark mark) { + HierarchyPos pos = new HierarchyPos(mark); + getRootVirtualGroup().updateTreeLayout(pos); + + /* + int index = Math.max(components.indexOf(mark) - 1, 0); + invalidateController(components.get(index)); + invalidateInteractor();*/ + } + + @Override + public Mark getHighlightedChild() { + if(highlighted) return this; + for(Mark mark:components) { + if(mark.getHighlightedChild() != null) return mark; + } + + return null; + + } + + @Override + public boolean isHighlighted() { + if(getHighlightedChild() == null) return false; + else return true; + } + + @Override + public void invalidated(Observable observable) {} + + + ////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////// + public boolean stickToYAxis() { + if(components.isEmpty()) return false; + if(alignXProperty.get() == YSticky.Yes) { + if(constraintXProperty.get() == Constraint.None) { + for(Mark mark: components) { + mark.coords.x.set(0); + } + } else { + components.get(0).coords.x.set(0); + } + return true; + } else return false; + } + + public boolean stickToXAxis() { + if(components.isEmpty()) return false; + if(alignYProperty.get() == XSticky.Yes) { + if(constraintYProperty.get() == Constraint.None) { + for(Mark mark: components) { + mark.coords.y.set(0); + } + } else { + components.get(0).coords.y.set(0); + } + return true; + } else return false; + } + + + public Mark getMarkAt(HierarchyPos pos) { + Integer index = pos.getIndexAt(level - 1); + if(index == null) return null; + else { + Mark mark = components.get(index); + if(mark instanceof VisBody) { + return ((VisBody)mark).getMarkAt(pos); + } else return mark; + } + } + + ////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////// + // TODO.... + public void updateLayout(Mark mark) { + if(!toUpdateLayout) return; + + //Mark mark = (Mark)((Property)observable).getBean(); + HierarchyPos pos = new HierarchyPos(mark); + getRootVirtualGroup().updateTreeLayout(pos); + //invalidateControllerAt(components.indexOf(mark)); + } + + + // TODO: This works fine although I update everything!!! + // I could only update invalidated nodes for higher performance (invisible though)!!!! + public void updateTreeLayout(HierarchyPos pos) { + boolean stickToY = stickToYAxis(); + boolean stickToX = stickToXAxis(); + //////////////////// + + Integer index = pos.getIndexAt(level - 1); + + if(index == null) return; + else if(level > pos.getBottomLevel() && toUpdateLayout) { + for(Mark mark: components) { + if(mark instanceof VisBody) { + ((VisBody)mark).updateTreeLayout(pos); + } + } + } + + invalidateControllerAt(index, stickToX, stickToY); + } + + + protected void invalidateControllerAt(int index, boolean stickToX, boolean stickToY) { + index = Math.min(components.size() - 1, Math.max(index, 0)); + + Mark mark = components.get(stickToY ? 0 : index); + controllerX.updateMarkPositions(mark); + + mark = components.get(stickToX ? 0 : index); + controllerY.updateMarkPositions(mark); + + mark = components.get(index); + if(mark.getHighlightedChild() != null) { // TODO: any problems with that? + if(controllerX.isVisible()) controllerX.setMark(mark, true); + if(controllerY.isVisible()) controllerY.setMark(mark, true); + } + + invalidateInteractor(); + } + ////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////// + + + public void addToOuterCollection(List<VisBody> list) { + VisBody outer = getRootVirtualGroup(); + if(outer instanceof VisCollection) list.add(outer); + } + + public void addToInnerCollections(List<VisBody> list) { + VisBody outer = getRootVirtualGroup(); + if(outer instanceof VisCollection) { + for(Mark mark: outer.components) { + if(mark instanceof VisCollection) + list.add((VisCollection)mark); + } + + if(list.isEmpty()) list.add(outer); + } + } + + public int getLevel() { + return level; + } + + @Override + public double width() { + return interactor.getWidth(); + } + + @Override + public double height() { + return interactor.getHeight(); + } + + + @Override + public double getLeft() { + return coords.getX() + interactor.left.get(); + } + + @Override + public double getRight() { + return coords.getX() + interactor.right.get(); + } + + @Override + public double getTop() { + return -(coords.getY() - interactor.top.get()); + } + + @Override + public double getBottom() { + return -(coords.getY() - interactor.bottom.get()); + } + + + @Override + public Shadow getShadow(double dx, double dy, double delta) { + dx *= delta; + dy *= delta; + + return new Shadow(this, dx, dy, interactor.createCopy(dx, dy)); + } + + public Map<PropertyName, Property> getSelfProperties() { + Map<PropertyName, Property> map = new TreeMap<>(); + for(Property property: properties) map.put(new PropertyName(property.getName()), property); + return map; + } + + @Override + public void stealProperties(SimpleMark mark) { + super.stealProperties(mark); + if(mark instanceof VisBody) { + VisBody vgroup = (VisBody)mark; + + constraintXProperty.set(vgroup.constraintXProperty.get()); + constraintXProperty.getPublicProperty().set(vgroup.constraintXProperty.getPublicProperty().get()); + + constraintYProperty.set(vgroup.constraintYProperty.get()); + constraintYProperty.getPublicProperty().set(vgroup.constraintYProperty.getPublicProperty().get()); + + // TODO: + distanceXProperty.getPublicProperty().set(vgroup.distanceXProperty.getPublicProperty().get()); + distanceYProperty.getPublicProperty().set(vgroup.distanceYProperty.getPublicProperty().get()); + distanceXProperty.getActiveProperty().set(vgroup.distanceXProperty.getActiveProperty().get()); + distanceYProperty.getActiveProperty().set(vgroup.distanceYProperty.getActiveProperty().get()); + + distanceXProperty.set(vgroup.distanceXProperty.get()); + distanceYProperty.set(vgroup.distanceYProperty.get()); + + + alignXProperty.set(vgroup.alignXProperty.get()); + alignXProperty.getPublicProperty().set(vgroup.alignXProperty.getPublicProperty().get()); + + alignYProperty.set(vgroup.alignYProperty.get()); + alignYProperty.getPublicProperty().set(vgroup.alignYProperty.getPublicProperty().get()); + } + } + + + protected abstract VisBody getInstance(Container container, boolean toEnd); + + @Override + public SimpleMark createCopy(Container container, double dx, double dy) { + double x = coords.getX() + dx; + double y = coords.getY() + dy; + + VisBody vgroup = getInstance(container, container.addToEnd(x, y)); + vgroup.setLevel(this.level); + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + vgroup.setRefCoords(refCoords); + + List<Mark> marks = new ArrayList<>(); + for(Mark mark: components) { + if(mark instanceof SimpleMark) { + SimpleMark simpleMark = (SimpleMark)mark; + SimpleMark copy = simpleMark.createCopy(vgroup, 0, 0); + copy.initProperties(); + marks.add(copy); + } + } + + for(Mark mark: marks) { + vgroup.attachCopy(mark/*, childPropertyStructure.isXShared(), childPropertyStructure.isYShared()*/); + } + + vgroup.setPropertyStructure(PropertyStructure.create(marks, childPropertyStructure)); + + vgroup.toUpdate(false); // Protect the parent tree from property updates + vgroup.stealProperties(this); + vgroup.coords.x.set(x); + vgroup.coords.y.set(y); + vgroup.coords.xRef.set(RefX.Left); + vgroup.coords.yRef.set(RefY.Bottom); + vgroup.toUpdate(true); + + return vgroup; + } + + + public double left() { + double left; + left = coords.getX() + interactor.left.get(); // TODO : FREEEEE + + return left; + } + + public double right() { + double right; + right = coords.getX() + interactor.right.get(); // TODO : FREEEEE + + return right; + } + + public double centerX() { + double center; + center = coords.getX(); + + return center; + } + + public double top() { + if(components.size() == 1 || constraintYProperty.get() != DistributionProperty.Constraint.None) { + return coords.getY() + components.get(components.size() - 1).top(); + } + else { + return coords.getY() - interactor.top.get(); + } + } + + public double bottom() { + if(components.size() == 1 || constraintYProperty.get() != DistributionProperty.Constraint.None) { + return coords.getY() + components.get(0).bottom(); + } + else { + return coords.getY() - interactor.bottom.get(); + } + } + + public double centerY() { + double center; + center = coords.getY(); + + return center; + } + + @Override + public VisBody getRootVirtualGroup() { + if(container instanceof VisBody) + return ((VisBody) container).getRootVirtualGroup(); + else return this; + } + + @Override + public String getNestingPropertyName(String name) { + StringBuffer buffer = new StringBuffer(""); + + buffer.append(PropertyName.getPrefix(level)); + buffer.append(name); + + VisGroup topGroup = getTopGroup(); + if(topGroup != null && topGroup != this) { + String id = createID(); + String pid = topGroup.createID() + "."; + buffer.append(id.replaceAll(pid, "")); + } + + return buffer.toString(); + } + + + public boolean isExternal() { + return !(container instanceof VisBody); + } + + public void highlighted(Mark mark, boolean highlight) { + if(controllerX != null) controllerX.setMark(mark, highlight); + if(controllerY != null) controllerY.setMark(mark, highlight); + + /* + if(container instanceof VisBody) + ((VisBody) container).highlighted(this, highlight);*/ + } + + + public void showGroupInteractors(boolean show) { + super.showGroupInteractors(show); + interactor.setVisible(show); + } + + public VisBody getVirtualGroup() { + return this; + } + + @Override + public void mouseRelease() { + if(controllerX != null) controllerX.unselectHandle(); + if(controllerY != null) controllerY.unselectHandle(); + if(container instanceof VisBody) container.getVirtualGroup().mouseRelease(); + } + + + @Override + public void bringToFront(Mark mark) { + if(components.get(components.size()-1) == mark) return; + components.remove(mark); + components.add(mark); + + shapes.getChildren().remove(mark.group); + shapes.getChildren().add(mark.group); + } + + @Override + public void sendToBack(Mark mark) { + if(components.get(0) == mark) return; + + components.remove(mark); + components.add(0, mark); + + shapes.getChildren().remove(mark.group); + shapes.getChildren().add(0, mark.group); + } + + @Override + public void updateReordering() { + updateReordering(components); + } + + public void setPropertyStructure(PropertyStructure propertyStructure) { + childPropertyStructure = propertyStructure; + childPropertyStructure.setContainer(this); + } + + public boolean hasChildProperties() { + return childPropertyStructure != null && !childPropertyStructure.isEmpty(); + } + + public PropertyStructure getChildPropertyStructure(){ + return childPropertyStructure; + } + + + // TODO: May need to change -- when axes are hidden, not possible to see what's going on + public double getControlYPosition() { + return 0; + } + + public double getControlXPosition() { + return 0; + } + + public void toUpdate(boolean toUpdate) { + this.toUpdateLayout = toUpdate; + } + + + @Override + public Property getProperty(PropertyName name) { + if(name.isDistributionX()) return constraintXProperty; + else if(name.isDistributionY()) return constraintYProperty; + else if(name.isDeltaX()) return distanceXProperty; + else if(name.isDeltaY()) return distanceYProperty; + else if(name.isStickyX()) return alignYProperty; + else if(name.isStickyY()) return alignXProperty; + else return super.getProperty(name); + } +} diff --git a/src/fr/inria/structgraphics/graphics/VisCollection.java b/src/fr/inria/structgraphics/graphics/VisCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..fac858357e69c6f99c4e5ce9f8dd394c366bc345 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/VisCollection.java @@ -0,0 +1,142 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.Axis; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.AxisX; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.AxisY; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.BodyInteractor; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.CollectionInteractor; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.LegendCollection; +import javafx.beans.property.Property; + +public class VisCollection extends VisBody { + + protected Axis axisX, axisY; + protected LegendCollection legends; + + public VisCollection(Container parent, boolean toend) { + super(parent, toend); + } + + protected BodyInteractor createInteractor() { // TODO:... + return new CollectionInteractor(this); + } + + public void showVariableOnAxis(DataVariable variable, boolean parent) { + if(variable.isX() || variable.isWidth()) { + if(parent) axisX.showVariableOnParent(variable); + else axisX.showVariable(variable); + } + else if(variable.isY() || variable.isHeight()) { + if(parent) axisY.showVariableOnParent(variable); + else axisY.showVariable(variable); + } + } + + + @Override + public CollectionInteractor getInteractor() { + return (CollectionInteractor)interactor; + } + + public void showVariableOnLegend(DataVariable variable) { + legends.addLegend(variable); + } + + @Override + public void addExtraComponents() { + axisX = new AxisX(this); + group.getChildren().add(axisX); + axisY = new AxisY(this); + group.getChildren().add(axisY); + legends = new LegendCollection(this); + group.getChildren().add(legends); + } + + @Override + public void updateExtraComponents() { + axisX.update(); + axisY.update(); + legends.update(); + } + + @Override + public String getName() { + //return "Collection"; + return PropertyName.getPrefix(level) + " Collection"; + } + + @Override + public String getType() { + return "Collection"; + } + + @Override + protected VisBody getInstance(Container container, boolean toEnd) { + return new VisCollection(container, toEnd); + } + + @Override + public void addSelfProperties(boolean toCollection, Map<PropertyName, FlexibleListProperty> table) { + Collection<FlexibleListProperty> childProperties; + if(childPropertyStructure != null) childProperties = ((CollectionPropertyStructure)childPropertyStructure).getProperties().values(); + else childProperties = new ArrayList<>(); + + if(table.isEmpty()) { + for(Property property: properties) { + FlexibleListProperty list = FlexibleListProperty.createList(this, property); + table.put(new PropertyName(list.getName()), list); + } + for(Property property: childProperties) { + FlexibleListProperty list = FlexibleListProperty.createList(this, property); + table.put(new PropertyName(list.getName()), list); + } + } else { + for(Property property: properties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + if(list!=null) { + if(isFirst()) list.add(0, property); + else list.add(property); + } + } + for(Property property: childProperties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + if(list!=null) { + if(isFirst()) list.add(0, property); + else list.add(property); + } + } + } + } + + @Override + public boolean removeSelfProperties(Map<PropertyName, FlexibleListProperty> table) { + boolean removed = super.removeSelfProperties(table); + + Collection<FlexibleListProperty> childProperties = ((CollectionPropertyStructure)childPropertyStructure).getProperties().values(); + for(Property property: childProperties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + if(list!=null && list.remove(property)) removed = true; + } + + return removed; + } + + @Override + public CollectionPropertyStructure getChildPropertyStructure(){ + return (CollectionPropertyStructure)super.getChildPropertyStructure(); + } + + public void addID(List<Property> ids, int level) { + if(level == this.level) ids.add(id); + else if(container instanceof VisCollection) ((VisCollection)container).addID(ids, level); + } +} diff --git a/src/fr/inria/structgraphics/graphics/VisFrame.java b/src/fr/inria/structgraphics/graphics/VisFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..9ac954f3a6eebbe40370d2835a8d10f8dfd8b4fa --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/VisFrame.java @@ -0,0 +1,167 @@ +package fr.inria.structgraphics.graphics; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.FillColorProperty; +import fr.inria.structgraphics.types.HeightProperty; +import fr.inria.structgraphics.types.StrokeColorProperty; +import fr.inria.structgraphics.types.WidthProperty; +import fr.inria.structgraphics.ui.inspector.InspectorView; +import javafx.beans.property.DoubleProperty; +import javafx.geometry.Point2D; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Rectangle; + +public class VisFrame extends Container { + + public DoubleProperty width, height; + protected FillColorProperty paint = new FillColorProperty(this); + public StrokeColorProperty stroke = new StrokeColorProperty(this); + private Rectangle frame; + + private InspectorView inspector; + + private Rectangle bedsheet = null; + + public VisFrame(double width, double height){ + super(); + + this.width = new WidthProperty(this, width); + this.height = new HeightProperty(this, height); + + frame = new Rectangle(0, 0, width, height); // Bind with rectangle's dimensions + this.width.bindBidirectional(frame.widthProperty()); + this.height.bindBidirectional(frame.heightProperty()); + + // paint.set(Color.web("#f1f1e8")); + paint.set(Color.WHITE); + frame.setFill(paint.get()); + + stroke.set(Color.GREY); + + group.getChildren().add(frame); + } + + @Override + public void initProperties() { + addProperty(this.width); + addProperty(this.height); + addProperty(this.paint); + addProperty(this.stroke); + //super.initProperties(); + } + + public void setInspector(InspectorView inspector) { + this.inspector = inspector; + } + + public InspectorView getInspector() { + return inspector; + } + + @Override + public Point2D getReferencePoint(double cx, double cy) { + double x, y; + + if(refCoords.containerXRef.get() == RefX.Left) x = cx; + else if(refCoords.containerXRef.get() == RefX.Right) x = width.get() + cx; + else x = width.get()/2 + cx; + + if(refCoords.containerYRef.get() == RefY.Top) y = -cy; + else if(refCoords.containerYRef.get() == RefY.Bottom) y = height.get() - cy; + else y = height.get()/2 - cy; + + return new Point2D(x, y); + } + + @Override + public double getRefAngle(double x) { + return 0; + } + + @Override + public String getName() { + return "Root Frame"; + } + + @Override + public String getType() { + return "Pane"; + } + + + @Override + public void updateShape() { + frame.setFill(paint.get()); + } + + + public double getRootWidth() { + return width.get(); + } + + public double getRootHeight() { + return height.get(); + } + + @Override + public void bringToFront(Mark mark) { + if(components.get(components.size()-1) == mark) return; + + components.remove(mark); + components.add(mark); + + group.getChildren().remove(mark.group); + group.getChildren().add(mark.group); + } + + @Override + public void sendToBack(Mark mark) { + if(components.get(0) == mark) return; + + components.remove(mark); + components.add(0, mark); + + group.getChildren().remove(mark.group); + group.getChildren().add(1, mark.group); + } + + @Override + public void updateReordering() { + for(Mark mark:components) mark.refreshID(); + getInspector().updateOrder(); + } + + public Rectangle createBedSheet() { + if(bedsheet == null) { + bedsheet = new Rectangle(0, 0, width.get(), height.get()); + bedsheet.setFill(Color.rgb(255, 255, 255, 0.6)); + } else { + bedsheet.setWidth(width.get()); + bedsheet.setHeight(height.get()); + } + + return bedsheet; + } + + public Rectangle getBedSheet() { + return bedsheet; + } + + @Override + public ShapeMark getShape(double x, double y) { + for(Mark mark: components) { + if(mark instanceof LineConnectedCollection) { // Only consider the shapes somewhere within collections + double x_ = x - mark.coords.getX(); + double y_ = (height.get() - y) - mark.coords.getY(); + + ShapeMark mark_ = mark.getShape(x_, y_); + if(mark_ != null) return mark_; + } + } + + return null; + } +} + diff --git a/src/fr/inria/structgraphics/graphics/VisGroup.java b/src/fr/inria/structgraphics/graphics/VisGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..382cd51d2a9cc1f4f74f36a12145fa63107be19b --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/VisGroup.java @@ -0,0 +1,309 @@ +package fr.inria.structgraphics.graphics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.types.XProperty; +import fr.inria.structgraphics.types.YProperty; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.GroupPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.BodyInteractor; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.CollectionInteractor; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.GroupInteractor; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.SimpleGroupInteractor; +import javafx.beans.property.Property; +import javafx.scene.shape.Line; + +public class VisGroup extends VisBody { + + protected ArrayList<Property> allProperties; + + public VisGroup(Container parent, boolean toend) { + super(parent, toend); + // TODO Auto-generated constructor stub + } + + @Override + public String getName() { + return PropertyName.getPrefix(level) + " Group"; + } + + protected BodyInteractor createInteractor() { // TODO:... + return new GroupInteractor(this); + } + + @Override + protected VisBody getInstance(Container container, boolean toEnd) { + return new VisGroup(container, toEnd); + } + + @Override + public BodyInteractor getInteractor() { + return interactor; + } + + @Override + public void addLeafIDs(List<Property> ids){ + ids.add(id); + } + + @Override + public void addIDs(List<Property> ids, int level) { + if(this.level == level) ids.add(id); + else if(container instanceof VisCollection) ((VisCollection)container).addID(ids, level); + } + + @Override + public int leafs() { + return 1; + } + + @Override + public int getBottomLevel() { + return level; + } + + @Override + public void addSelfProperties(boolean toCollection, Map<PropertyName, FlexibleListProperty> table) { + + if(toCollection) { + for(Property property: allProperties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + if(list!=null) { + if(isFirst()) list.add(0, property); + else list.add(property); + } + else { + list = FlexibleListProperty.createList(this, property, true); + table.put(new PropertyName(property.getName()), list); + } + } + } else { + Collection<FlexibleListProperty> childProperties; + if(childPropertyStructure != null) childProperties = ((GroupPropertyStructure)childPropertyStructure).getProperties().values(); + else childProperties = new ArrayList<>(); + + for(Property property: properties) { + FlexibleListProperty list = table.get(new PropertyName(PropertyName.getCleanName(property.getName()))); + + if(list!=null) { + if(isFirst()) list.add(0, property); + else list.add(property); + } + else { + list = FlexibleListProperty.createList(this, property); + table.put(new PropertyName(list.getName()), list); + } + } + for(FlexibleListProperty property: childProperties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + + if(list!=null) { + if(isFirst()) list.addEach(0, property.flatten()); + else list.addEach(property.flatten()); + } + else { + list = FlexibleListProperty.createList(this, property.flatten()); + table.put(new PropertyName(property.getName()), list); + } + } + } + } + + + @Override + public boolean removeSelfProperties(Map<PropertyName, FlexibleListProperty> table) { // TODO: Check what need to be done!!!! + boolean removed = super.removeSelfProperties(table); + + Collection<FlexibleListProperty> childProperties = ((GroupPropertyStructure)childPropertyStructure).getProperties().values(); + for(Property property: childProperties) { + FlexibleListProperty list = table.get(new PropertyName(property.getName())); + if(list!=null && list.remove(property)) removed = true; + } + + return removed; + } + + @Override + public void initVisibility() { + super.initVisibility(); + + for(Property property:properties) { // TODO: CHECK for nested groups !!! + if(property instanceof Shareable) { + PropertyName name = new PropertyName(property.getName()); + ((Shareable)property).getPublicProperty().set(name.isX() || name.isY()); + } + } + } + + @Override + public void initProperties() { + super.initProperties(); + + ArrayList<Property> publicProperties = new ArrayList<>(); + ArrayList<Property> allProperties = new ArrayList<>(); + splitProperties(publicProperties, allProperties); + } + + @Override + public SimpleMark createCopy(Container container, double dx, double dy) { // TODO: Correct? + VisGroup copy = (VisGroup)super.createCopy(container, dx, dy); + + return copy; + } + + @Override + public void pin() { + if(container instanceof VisGroup) { + super.pin(); + for(Mark child: getComponents()) { + child.pin(); + } + } + else super.pin(); + } + + public void setPositionX(double x) { + /*if(container instanceof VisGroup) { + translateAllBy(x, 0); + } else*/ coords.x.set(x); + } + + public void setPositionY(double y) { + /*if(container instanceof VisGroup) { + translateAllBy(0, y); + } else*/ coords.y.set(y); + } + + + // Makes sure that all are translated by dx, dy without undesirable binding activations + protected void translateAllBy(double dx, double dy) { + for(Mark child: getComponents()) { + child.setTentative(child.coords.getX() + dx, child.coords.getY() + dy); + } + + for(Mark child: getComponents()) { + child.validateTentative(); + } + } + + // Makes sure that all are translated by dx, dy without undesirable binding activations + protected void translateAll(Line lineTrace) { + double dx = lineTrace.getEndX() - lineTrace.getStartX(); + double dy = -lineTrace.getEndY() + lineTrace.getStartY(); + for(Mark child: getComponents()) { + child.setTentative(child.pinX + dx, child.pinY + dy); + } + + for(Mark child: getComponents()) { + child.validateTentative(); + } + } + + @Override + public void attach(Mark mark, boolean sharedX, boolean sharedY) { + Container container = mark.getContainer(); + container.removeMark(mark); + + ////////// + for(Property property:mark.properties) { + if(property instanceof Shareable) { + if(mark instanceof VisGroup) { + if(property instanceof XProperty || property instanceof YProperty) ((Shareable)property).getPublicProperty().set(false); + } + else ((Shareable)property).getPublicProperty().set(false); + } + } + + double xpos, ypos; + + RefX xref = mark.coords.xRef.get(); + RefY yref = mark.coords.yRef.get(); + + if(xref == RefX.Left) xpos = mark.left(); + else if (xref == RefX.Right) xpos = mark.right(); + else xpos = mark.centerX(); + + if(yref == RefY.Top) ypos = mark.top(); + else if (yref == RefY.Bottom) ypos = mark.bottom(); + else ypos = mark.centerY(); + + mark.setPositionCoords(sharedX ? 0 : xpos - coords.getX(), sharedY ? 0 : ypos - coords.getY()); + ///////// + + addMark(mark, true); + } + + @Override + public GroupPropertyStructure getChildPropertyStructure(){ + return (GroupPropertyStructure)super.getChildPropertyStructure(); + } + + + @Override + public VisGroup getTopGroup() { + if(container instanceof VisGroup) return ((VisGroup) container).getTopGroup(); + else return this; + } + + public boolean isTopGroup() { + if(container instanceof VisGroup) return false; + else return true; + } + + public void splitProperties(ArrayList<Property> publicProperties, ArrayList<Property> allProperties) { + Collection<Property> selfProperties = getSelfProperties().values(); + for(Property prop:selfProperties) { + if(prop instanceof Shareable) { + allProperties.add(prop); + if(((Shareable)prop).getPublicProperty().get()) publicProperties.add(prop); + } + } + + // Children properties??? + Collection<FlexibleListProperty> childproperties = childPropertyStructure.getProperties().values(); + // TODO: ???? + for(FlexibleListProperty props: childproperties) { + PropertyName name = props.getPropertyName(); + if(childPropertyStructure.getCommon().contains(name)) { + ArrayList<GroupBinding> groups = ((GroupPropertyStructure)childPropertyStructure).getGroups(name); + if(groups != null) { + for(GroupBinding group: groups) { + if(!group.isEmpty()) { + Property first = group.firstProperty(); + if(((Shareable)first).getPublicProperty().get()) publicProperties.add(first); + for(Property property: (ArrayList<Property>)group.getProperties()) { + allProperties.add(property); + ((Shareable)property).getHiddenProperty().set(property != first); + } + } + } + } + } else { + for(Property prop: props.getValue()) { + if(prop instanceof Shareable) { + ((Shareable)prop).getHiddenProperty().set(false); + allProperties.add(prop); + if(((Shareable)prop).getPublicProperty().get()) publicProperties.add(prop); + } + } + } + } + + this.allProperties = allProperties; + } + + public ArrayList<Property> getAllProperties(){ + return allProperties; + } +} diff --git a/src/fr/inria/structgraphics/graphics/XMarkComparator.java b/src/fr/inria/structgraphics/graphics/XMarkComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..4c15433196289062191a86cddade8cb3b111c25f --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/XMarkComparator.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.graphics; + +import java.util.Comparator; + +public class XMarkComparator implements Comparator<Mark> { + + @Override + public int compare(Mark mark1, Mark mark2) { + return ((Double)mark1.coords.x.get()).compareTo((Double)mark2.coords.x.get()); + } + +} diff --git a/src/fr/inria/structgraphics/graphics/YMarkComparator.java b/src/fr/inria/structgraphics/graphics/YMarkComparator.java new file mode 100644 index 0000000000000000000000000000000000000000..a2a549d70acae27b304f3dd9c8131971a12a6a1c --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/YMarkComparator.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.graphics; + +import java.util.Comparator; + +public class YMarkComparator implements Comparator<Mark> { + + @Override + public int compare(Mark mark1, Mark mark2) { + return ((Double)mark1.coords.y.get()).compareTo((Double)mark2.coords.y.get()); + } + +} diff --git a/src/fr/inria/structgraphics/graphics/controls/Control.java b/src/fr/inria/structgraphics/graphics/controls/Control.java new file mode 100644 index 0000000000000000000000000000000000000000..9e9155707cdcc79f60a04be55804fea6357dd84a --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/controls/Control.java @@ -0,0 +1,73 @@ +package fr.inria.structgraphics.graphics.controls; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Line; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; + +public class Control extends Rectangle { + private final static double SIZE = 6; + + protected ControlListener listener; + + public Control() { + super(SIZE, SIZE); + setStroke(Color.GRAY); + setStrokeWidth(0.5); + setFill(Color.rgb(255, 255, 255, 0.5)); + } + + public double centerX() { + return getX() + getWidth()/2; + } + + public double centerY() { + return getY() + getWidth()/2; + } + + public void moveTo(double x, double y) { + xProperty().set(x - getWidth()/2); + yProperty().set(y - getWidth()/2); + } + + public boolean intersects(Shape trace) { + if(trace == null) return false; + Shape intersection = Shape.intersect(this, trace); + if(!(intersection instanceof Path) || !((Path)intersection).getElements().isEmpty()) + return true; + else return false; + } + + public void setControlListener(ControlListener listener) { + this.listener = listener; + } + + public void pin() { + if(listener!=null) listener.pin(); + } + + public void drag(Line lineTrace) { + if(listener!=null) listener.drag(lineTrace); + } + + + public interface ControlListener { + public void drag(Line lineTrace); + public void pin(); + public void offsetX(double offset); + public void offsetY(double offset); + } + + + public void offsetXPin(double offset) { + if(listener!=null) listener.offsetX(offset); + } + + public void offsetYPin(double offset) { + if(listener!=null) listener.offsetY(offset); + } + +} + + diff --git a/src/fr/inria/structgraphics/graphics/splines/CubicSpline.java b/src/fr/inria/structgraphics/graphics/splines/CubicSpline.java new file mode 100644 index 0000000000000000000000000000000000000000..4431d357e91033fbf43da032198475f5e936cbab --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/splines/CubicSpline.java @@ -0,0 +1,229 @@ +package fr.inria.structgraphics.graphics.splines; + +import java.awt.geom.Path2D; +import java.util.ArrayList; +import java.util.List; + +import javafx.geometry.Point2D; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; + +/** + * A cubic spline. + * @author Theophanis Tsandilas + */ +public class CubicSpline { + + private List<Vector2D> backwardTangents = new ArrayList<Vector2D>(); + private List<Point2D> controlPoints = new ArrayList<Point2D>(); + private List<Vector2D> forwardTangents = new ArrayList<Vector2D>(); + private Path linePath; + private Path path; + private List<Point2D> pathPoints = new ArrayList<Point2D>(); + + private Path2D awtpath; // Used to flatten the curve (not available methods in FX) + + // .3 is pretty good... + private double tension = .3; + + + public CubicSpline() { + path = new Path(); + } + + public CubicSpline(Path path) { + this.path = path; + } + + public void clear(){ + backwardTangents.clear(); + controlPoints.clear(); + forwardTangents.clear(); + linePath = new Path(); + path.getElements().clear(); + awtpath = new Path2D.Double(); + + pathPoints.clear(); + } + + public List<Vector2D> getBackwardTangents() { + return backwardTangents; + } + + public List<Point2D> getControlPoints() { + return controlPoints; + } + + /** + * @return + */ + public List<Vector2D> getForwardTangents() { + return forwardTangents; + } + + /** + * @return + */ + public Shape getLineShape() { + return linePath; + } + + /** + * @return a copy of the path's points + */ + public List<Point2D> getPathPoints() { + return pathPoints; + } + + /** + * @return + */ + public Shape getShape() { + return path; + } + + /** + * Connect the points into line segments. + * + * @param points + */ + private void makeConnectedLines() { + Point2D p; + + if(pathPoints.size() > 0) { + p = pathPoints.get(0); + linePath.getElements().add(new MoveTo(p.getX(), p.getY())); + } + + for (int i = 1; i < pathPoints.size(); i++) { + p = pathPoints.get(i); + linePath.getElements().add(new LineTo(p.getX(), p.getY())); + } + } + + /** + * + */ + private void makePiecewiseBezier() { + //path.getElements().clear(); + + Point2D p1, c1, c2, p2; + + int j = 0; // control points index + for (int i = 0; i < pathPoints.size() - 1; i++) { + p1 = pathPoints.get(i); + c1 = controlPoints.get(j++); // get j, and increment it + c2 = controlPoints.get(j++); + p2 = pathPoints.get(i + 1); + + if(i == 0) path.getElements().add(new MoveTo(p1.getX(), p1.getY())); + path.getElements().add(new CubicCurveTo(c1.getX(), c1.getY(), c2.getX(), c2.getY(), p2.getX(), p2.getY())); + + // AWT object + awtpath.moveTo(p1.getX(), p1.getY()); + awtpath.curveTo(c1.getX(), c1.getY(), c2.getX(), c2.getY(), p2.getX(), p2.getY()); + } + } + + /**. + * + * @param points + */ + public void setPoints(List<Point2D> points) { + if(points == null || points.size() == 0) return; + + clear(); + + pathPoints.addAll(points); + + // if a single point, draw a dot + if (points.size() == 1) { + final Point2D point = points.get(0); + path.getElements().add(new MoveTo(point.getX(), point.getY())); + path.getElements().add(new LineTo(point.getX(), point.getY())); + + // AWT object + awtpath.moveTo(point.getX(), point.getY()); + awtpath.lineTo(point.getX(), point.getY()); + } + // if two points, draw a line segment + else if (points.size() == 2) { + final Point2D point1 = points.get(0); + final Point2D point2 = points.get(1); + path.getElements().add(new MoveTo(point1.getX(), point1.getY())); + path.getElements().add(new LineTo(point2.getX(), point2.getY())); + + // AWT object + awtpath.moveTo(point1.getX(), point1.getY()); + awtpath.lineTo(point2.getX(), point2.getY()); + return; + } + else { + // if three points or more, do the piecewise business... + // construct piecewise cubic bezier curves that generate the catmull-rom + + // tack on an extra last point, to enable the calculation of the last tangent + Point2D pSecondToLast = points.get(points.size() - 2); + Point2D pLast = points.get(points.size() - 1); + points.add(Vector2D.add(pLast, Vector2D.subtract(pLast, pSecondToLast))); + + // p0 is the previous point + Point2D p0 = Vector2D.add(points.get(0), Vector2D.subtract(points.get(0), points.get(1))); + // p1 is the current point + Point2D p1 = points.get(0); + // p2 is the next point + Point2D p2; + + // add tangents and control points + for (int i = 0; i < points.size() - 1; i++) { + p2 = points.get(i + 1); + + // === Backwards === + // add a backward tangent for each point, equal to the point before minus the point + // after + final Vector2D bkwd = Vector2D.getScaled(tension * Vector2D.subtract(p0, p1).magnitude(), + Vector2D.subtract(p0, p2)); + backwardTangents.add(bkwd); + + // make a backward control point by adding it to the current point + controlPoints.add(Vector2D.add(p1, bkwd)); + + // === Forwards === + // add a tangent for each point, that is equal to the point after minus the point before + // length is equal to half the distance to the next point + final Vector2D fwd = Vector2D.getScaled(tension * Vector2D.subtract(p2, p1).magnitude(), Vector2D + .subtract(p2, p0)); + forwardTangents.add(fwd); + + // make a forward control point by adding it to the current point + controlPoints.add(Vector2D.add(p1, fwd)); + + // shift the points over + p0 = p1; + p1 = p2; + } + + // delete first and last control points (as they are useless) + controlPoints.remove(0); + controlPoints.remove(controlPoints.size() - 1); + + // don't remove tangents + + // line segments + makeConnectedLines(); + + // general path, piecewise cubic bezier + makePiecewiseBezier(); + } + + } + + public FlattenedPath getFlattenedPath() { + return new FlattenedPath(awtpath); + } + +} + diff --git a/src/fr/inria/structgraphics/graphics/splines/FlattenedPath.java b/src/fr/inria/structgraphics/graphics/splines/FlattenedPath.java new file mode 100644 index 0000000000000000000000000000000000000000..d6f119861d1a9a548d7f281d0ba60fe99fa783b5 --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/splines/FlattenedPath.java @@ -0,0 +1,111 @@ +package fr.inria.structgraphics.graphics.splines; + +import java.awt.geom.Line2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.util.ArrayList; + +import javafx.geometry.Point2D; + +public class FlattenedPath { + + private final static double FLATNESS = 1; // maximum error for flattening the curve + + private ArrayList<Line2D> segments = new ArrayList<Line2D>(); + private ArrayList<Double> cumlength = new ArrayList<Double>(); // Cumulative length + private ArrayList<Double> angles = new ArrayList<Double>(); // In radius (not in degrees) + + private double length = 0; + + public FlattenedPath(Path2D awtPath) { + PathIterator pathIterator = awtPath.getPathIterator(null, FLATNESS); + double[] coords = new double[6]; + + double x = 0, y = 0; + for( ; !pathIterator.isDone(); pathIterator.next()) { + int type = pathIterator.currentSegment(coords); + + if(type == PathIterator.SEG_MOVETO) { + x = coords[0]; + y = coords[1]; + } + else { + Line2D segment = new Line2D.Double(x, y, coords[0], coords[1]); + double dist = segment.getP1().distance(segment.getP2()); + + x = coords[0]; + y = coords[1]; + + segments.add(segment); + cumlength.add(length); + length += dist; + + angles.add(calculateAngle(segment)); + } + } + } + + public FlattenedPath(ArrayList<Point2D> points) { + for(int i = 1; i < points.size(); ++i) { + Line2D segment = new Line2D.Double(points.get(i - 1).getX(), points.get(i - 1).getY(), + points.get(i).getX(), points.get(i).getY()); + double dist = segment.getP1().distance(segment.getP2()); + + segments.add(segment); + cumlength.add(length); + length += dist; + + angles.add(calculateAngle(segment)); + } + } + + public double getLength() { + return length; + } + + private double calculateAngle(Line2D segment) { + return Math.atan2(segment.getY1() - segment.getY2(), segment.getX2() - segment.getX1()); + } + + private int getIndex(double l) { + if(cumlength.size() == 0) return -1; + else if(cumlength.size() == 1) return 0; + + for(int i = 1; i < cumlength.size(); ++i) + if(l < cumlength.get(i)) return i - 1; + + return cumlength.size() - 1; + } + + private double getLength(int index) { + if(index == cumlength.size() - 1) + return length - cumlength.get(index); + else return cumlength.get(index + 1) - cumlength.get(index); + } + + public double getAngleInDegrees(double l) { + return Math.toDegrees(getAngleInRads(l)); + } + + public double getAngleInRads(double l) { + int index = getIndex(l); + + if(index == -1) return 0; + else return angles.get(index); + } + + public Point2D getPoint(double l) { + int index = getIndex(l); + if(index == -1) return null; + + double x0 = segments.get(index).getX1(); + double y0 = segments.get(index).getY1(); + double x1 = segments.get(index).getX2(); + double y1 = segments.get(index).getY2(); + + double ratio = (l - cumlength.get(index))/getLength(index); + + return new Point2D(x0 + ratio*(x1 - x0), y0 + ratio*(y1 - y0)); + } + +} diff --git a/src/fr/inria/structgraphics/graphics/splines/Vector2D.java b/src/fr/inria/structgraphics/graphics/splines/Vector2D.java new file mode 100644 index 0000000000000000000000000000000000000000..69fd251c1c5dc4c5f49549cc5ab0d837dabaca4d --- /dev/null +++ b/src/fr/inria/structgraphics/graphics/splines/Vector2D.java @@ -0,0 +1,118 @@ +/** + * + */ +package fr.inria.structgraphics.graphics.splines; + +import javafx.geometry.Point2D; + +/** + *taken from PaperToolkit. + */ +public class Vector2D { + + /** + * Adds a vector to a point, resulting in a new point. + * + * @param p + * @param v + * @return + */ + public static Point2D add(Point2D p, Vector2D v) { + return new Point2D(p.getX() + v.x, p.getY() + v.y); + } + + /** + * @param a + * @param b + * @return + */ + public static Vector2D add(Vector2D a, Vector2D b) { + final Vector2D result = new Vector2D(); + result.x = a.x + b.x; + result.y = a.y + b.y; + return result; + } + + /** + * @return + */ + public static Vector2D getNormalized(Vector2D v) { + return getScaled(1.0, v); + } + + /** + * @param desiredMagnitude + * @return + */ + public static Vector2D getScaled(double desiredMagnitude, Vector2D v) { + final double scaleFactor = desiredMagnitude / v.magnitude(); + final double newX = v.x * scaleFactor; + final double newY = v.y * scaleFactor; + return new Vector2D(newX, newY); + } + + /** + * @param d + * @param vector2D + * @return + */ + public static Vector2D multiply(double d, Vector2D vector2D) { + return new Vector2D(vector2D.x * d, vector2D.y * d); + } + + /** + * Returns a - b. + * + * @param a + * @param b + * @return + */ + public static Vector2D subtract(Point2D a, Point2D b) { + final Vector2D result = new Vector2D(); + result.x = a.getX() - b.getX(); + result.y = a.getY() - b.getY(); + return result; + } + + /** + * The X component. + */ + private double x = 0; + + /** + * The Y component. + */ + private double y = 0; + + public Vector2D() { + + } + + /** + * @param d + * @param e + */ + public Vector2D(double _x, double _y) { + x = _x; + y = _y; + } + + public double getX() { + return x; + } + + /** + * @return + */ + public double getY() { + return y; + } + + /** + * @return + */ + public double magnitude() { + return Math.sqrt(x * x + y * y); + } + +} diff --git a/src/fr/inria/structgraphics/persistence/JsonDescription.java b/src/fr/inria/structgraphics/persistence/JsonDescription.java new file mode 100644 index 0000000000000000000000000000000000000000..f03f40372c7a8e8db9eff9beac3a83b1ded63cd2 --- /dev/null +++ b/src/fr/inria/structgraphics/persistence/JsonDescription.java @@ -0,0 +1,61 @@ +package fr.inria.structgraphics.persistence; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonReader; + +public class JsonDescription { + private JsonObject visObject; + + public JsonDescription(File file) { + Path path = Paths.get(file.getPath()); + + StringBuffer textContent = new StringBuffer(); + List<String> lines; + try { + lines = Files.readAllLines(path, Charset.forName("ISO-8859-1")); + + for (String line : lines) { + textContent.append(line + "\n"); + } + + JsonReader reader = Json.createReader(new StringReader(textContent.toString())); + visObject = reader.readObject(); + reader.close(); + } catch (IOException e) { + visObject = null; + } + } + + public boolean isWorkspace() { + return visObject.containsKey("workspace"); + } + + public boolean isLibrary() { + return visObject.containsKey("library"); + } + + public JsonArray getVisArray(boolean workspace) { + if(visObject == null) return null; + else return workspace ? visObject.getJsonObject("workspace").getJsonArray("visualizations") : visObject.getJsonArray("visualizations"); + } + + public JsonArray getLibraryArray(boolean workspace) { + if(visObject == null) return null; + else return workspace ? visObject.getJsonObject("workspace").getJsonArray("library") : visObject.getJsonArray("library"); + } + + public JsonArray getSpreadsheetArray() { + return visObject.getJsonObject("workspace").getJsonArray("datasheet"); + } +} diff --git a/src/fr/inria/structgraphics/persistence/JsonObjectReader.java b/src/fr/inria/structgraphics/persistence/JsonObjectReader.java new file mode 100644 index 0000000000000000000000000000000000000000..cab2af2b2012c1f8583d025bf72e452b99decf0d --- /dev/null +++ b/src/fr/inria/structgraphics/persistence/JsonObjectReader.java @@ -0,0 +1,347 @@ +package fr.inria.structgraphics.persistence; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.json.JsonValue.ValueType; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.GenericShapeMark; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.PositionCoords; +import fr.inria.structgraphics.graphics.ReferenceCoords; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.TextualMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.ColoringSchemeProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.LineTypeProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.types.ColoringSchemeProperty.Scheme; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.types.ShapeProperty.Type; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnections; +import fr.inria.structgraphics.ui.viscanvas.groupings.GroupPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.PropertyStructure; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.scene.paint.Paint; + +public class JsonObjectReader { + + public static ArrayList<Mark> readChildrenFromJasonArray(Container parent, JsonArray jsonArray) { + ArrayList<Mark> marks = new ArrayList<>(); + + for(int i = 0; i < jsonArray.size(); ++i) { + JsonValue jsonObject = jsonArray.get(i); + + if(jsonObject instanceof JsonObject) { + Mark mark = readFromJasonObject(parent, (JsonObject)jsonObject); + if(mark != null) marks.add(mark); + } + } + + return marks; + } + + public static Mark readFromJasonObject(Container parent, JsonObject jsonObject) { + Mark mark; + + String type = jsonObject.getString("type"); + if(type.contains("Collection") || type.contains("Chart")) { // COLLECTIONS + mark = new LineConnectedCollection(parent, true); + + double thickness = jsonObject.getJsonNumber("thickness").doubleValue(); + mark.border.set(thickness); + String stroke = jsonObject.getString("stroke"); + mark.strokePaint.set(Paint.valueOf(stroke)); + double rotation = jsonObject.getJsonNumber("rotation").doubleValue(); + mark.angle.set(rotation); + + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + mark.setRefCoords(refCoords); + + double x = jsonObject.getJsonNumber("x").doubleValue(); + double y = jsonObject.getJsonNumber("y").doubleValue(); + PositionCoords coords = new PositionCoords(mark, x, y); + coords.xRef.set(RefX.Left); + coords.yRef.set(RefY.Bottom); + mark.coords = coords; + + mark.setLevel(jsonObject.getJsonNumber("level").intValue()); + + String stickX = jsonObject.getString("sticky-x"); + ((VisBody)mark).alignYProperty.set(XSticky.valueOf(stickX)); + String stickY = jsonObject.getString("sticky-y"); + ((VisBody)mark).alignXProperty.set(YSticky.valueOf(stickY)); + + String distrX = jsonObject.getString("distribution-x"); + ((VisBody)mark).constraintXProperty.set(Constraint.valueOf(distrX)); + String distrY = jsonObject.getString("distribution-y"); + ((VisBody)mark).constraintYProperty.set(Constraint.valueOf(distrY)); + + double deltaX = jsonObject.getJsonNumber("delta-x").doubleValue(); + ((VisBody)mark).distanceXProperty.set(deltaX); + if(((VisBody)mark).constraintXProperty.get() == Constraint.None) + ((VisBody)mark).distanceXProperty.getActiveProperty().set(false); + + double deltaY = jsonObject.getJsonNumber("delta-y").doubleValue(); + ((VisBody)mark).distanceYProperty.set(deltaY); + if(((VisBody)mark).constraintYProperty.get() == Constraint.None) + ((VisBody)mark).distanceYProperty.getActiveProperty().set(false); + + String curve = jsonObject.getString("curve"); + ((LineConnectedCollection)mark).lineProperty = new LineTypeProperty(mark, LineTypeProperty.Type.valueOf(curve)); + + JsonArray componentsArray = jsonObject.getJsonArray("components"); + Collection<Mark> children = readChildrenFromJasonArray(mark, componentsArray); + for(Mark child: children) { + ((VisBody)mark).attachCopy(child); + } + + JsonArray commonArray = jsonObject.getJsonArray("common"); + JsonArray variableArray = jsonObject.getJsonArray("variable"); + + ///// Construct the property structure + ((VisCollection)mark).setPropertyStructure(new CollectionPropertyStructure(children, + readPropertyNames(commonArray), readPropertyNames(variableArray))); + + //// CONNECTIONS + JsonArray connectionsArray = jsonObject.getJsonArray("connections"); + if(connectionsArray != null) { + String fill = jsonObject.getString("fill"); + ((LineConnectedCollection)mark).flowPaint.set(Paint.valueOf(fill)); + + double opacity = jsonObject.getJsonNumber("opacity").doubleValue(); + ((LineConnectedCollection)mark).flowOpacity.set(opacity); + + String coloringScheme = jsonObject.getString("coloring"); + ((LineConnectedCollection)mark).coloringScheme.set(ColoringSchemeProperty.Scheme.valueOf(coloringScheme)); + + if(((LineConnectedCollection)mark).coloringScheme.get() != Scheme.Common) + ((LineConnectedCollection)mark).flowPaint.getActiveProperty().set(false); + + FlowConnections connections = readConnections(connectionsArray, (LineConnectedCollection)mark); + ((LineConnectedCollection)mark).setFlowConnections(connections); + } + + mark.initProperties(); + + } else if(type.contains("Group")) { // GROUPS + mark = new VisGroup(parent, true); + + double rotation = jsonObject.getJsonNumber("rotation").doubleValue(); + mark.angle.set(rotation); + + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + mark.setRefCoords(refCoords); + + double x = jsonObject.getJsonNumber("x").doubleValue(); + double y = jsonObject.getJsonNumber("y").doubleValue(); + PositionCoords coords = new PositionCoords(mark, x, y); + coords.xRef.set(RefX.Left); + coords.yRef.set(RefY.Bottom); + mark.coords = coords; + + mark.setLevel(jsonObject.getJsonNumber("level").intValue()); + + String stickX = jsonObject.getString("sticky-x"); + ((VisBody)mark).alignYProperty.set(XSticky.valueOf(stickX)); + String stickY = jsonObject.getString("sticky-y"); + ((VisBody)mark).alignXProperty.set(YSticky.valueOf(stickY)); + + String distrX = jsonObject.getString("distribution-x"); + ((VisBody)mark).constraintXProperty.set(Constraint.valueOf(distrX)); + String distrY = jsonObject.getString("distribution-y"); + ((VisBody)mark).constraintYProperty.set(Constraint.valueOf(distrY)); + + double deltaX = jsonObject.getJsonNumber("delta-x").doubleValue(); + ((VisBody)mark).distanceXProperty.set(deltaX); + if(((VisBody)mark).constraintXProperty.get() == Constraint.None) + ((VisBody)mark).distanceXProperty.getActiveProperty().set(false); + + double deltaY = jsonObject.getJsonNumber("delta-y").doubleValue(); + ((VisBody)mark).distanceYProperty.set(deltaY); + if(((VisBody)mark).constraintYProperty.get() == Constraint.None) + ((VisBody)mark).distanceYProperty.getActiveProperty().set(false); + + /////////////// + JsonArray componentsArray = jsonObject.getJsonArray("components"); + Collection<Mark> children = readChildrenFromJasonArray(mark, componentsArray); + for(Mark child: children) { + ((VisBody)mark).attachCopy(child); + } + + JsonArray commonArray = jsonObject.getJsonArray("common"); + JsonArray variableArray = jsonObject.getJsonArray("variable"); + + ///// Construct the property structure + ((VisGroup)mark).setPropertyStructure(new GroupPropertyStructure(children, + readPropertyNames(commonArray), readPropertyNames(variableArray))); + + // Create and add bindings + JsonArray bindingsArray = jsonObject.getJsonArray("bindings"); + if(bindingsArray != null) { + TreeMap<PropertyName, ArrayList<GroupBinding>> bindings = readBindings(bindingsArray, (VisGroup)mark); + ((VisGroup)mark).getChildPropertyStructure().setBindings(bindings); + } + + mark.initProperties(); + + JsonArray publicArray = jsonObject.getJsonArray("public"); + if(publicArray != null) { + SortedSet<PropertyName> publicProperties = readPropertyNames(publicArray); + for(Property property : ((VisGroup)mark).getAllProperties()) { //System.err.println(property.getName()); + if(publicProperties.contains(new PropertyName(property.getName()))) + ((Shareable)property).getPublicProperty().set(true); + else ((Shareable)property).getPublicProperty().set(false); + } + } + + } else { // Shape Mark + ShapeProperty.Type shapeType; + + if(type.equals("Text")) { + shapeType = Type.Text; + } else { + String shape = jsonObject.getString("shape"); + if(shape.equals("Rectangle")) shapeType = Type.Rectangle; + else if(shape.equals("Ellipse")) shapeType = Type.Ellipse; + else if(shape.equals("Triangle")) shapeType = Type.Triangle; + else shapeType = Type.Line; + } + + double width = jsonObject.getJsonNumber("width").doubleValue(); + double height = jsonObject.getJsonNumber("height").doubleValue(); + mark = ShapeMark.createInstance(parent, true, shapeType, width, height); + + ((ShapeMark)mark).shapeProperty.set(shapeType); + + boolean lock = jsonObject.getString("lock").equals("true"); + mark.ratiolock.set(lock); + + double x = jsonObject.getJsonNumber("x").doubleValue(); + double y = jsonObject.getJsonNumber("y").doubleValue(); + PositionCoords coords = new PositionCoords(mark, x, y); + RefX referencex = ReferenceCoords.getXRef(jsonObject.getString("reference-x")); + RefY referencey = ReferenceCoords.getYRef(jsonObject.getString("reference-y")); + coords.xRef.set(referencex); + coords.yRef.set(referencey); + mark.coords = coords; + + double rotation = jsonObject.getJsonNumber("rotation").doubleValue(); + mark.angle.set(rotation); + + String fill = jsonObject.getString("fill"); + mark.paint.set(Paint.valueOf(fill)); + + if(shapeType == Type.Text) { + String text = jsonObject.getString("text"); + ((TextualMark)mark).textProperty.set(text); + double fontsize = jsonObject.getJsonNumber("fontsize").doubleValue(); + ((TextualMark)mark).fontSize.set(fontsize); + } else { + double thickness = jsonObject.getJsonNumber("thickness").doubleValue(); + mark.border.set(thickness); + String stroke = jsonObject.getString("stroke"); + mark.strokePaint.set(Paint.valueOf(stroke)); + } + + mark.initProperties(); + } + + ///////// + mark.update(); + + // TODO Auto-generated method stub + return mark; + } + + + private static SortedSet<PropertyName> readPropertyNames(JsonArray jsonArray){ + TreeSet<PropertyName> propertyNames = new TreeSet<>(); + + for(int i = 0; i < jsonArray.size(); ++i) { + PropertyName name = new PropertyName(jsonArray.getJsonString(i).getString()); + propertyNames.add(name); + } + + return propertyNames; + } + + private static FlowConnections readConnections(JsonArray jsonArray, LineConnectedCollection collection) { + FlowConnections connections = new FlowConnections(collection); + for(int i = 0; i < jsonArray.size(); ++i) { + JsonObject connObject = jsonArray.getJsonObject(i); + + String colId = collection.id.get(); + String originId = colId +"." + connObject.getString("origin"); + String destinationId = colId +"." + connObject.getString("destination"); + + double weight = connObject.getJsonNumber("weight").doubleValue(); + + FlowConnection connection = new FlowConnection(collection, + (ShapeMark)collection.getNodeWithId(originId), (ShapeMark)collection.getNodeWithId(destinationId)); + connection.weight.set(weight); + connections.add(connection); + } + + return connections; + } + + private static TreeMap<PropertyName, ArrayList<GroupBinding>> readBindings(JsonArray jsonArray, VisGroup group) { + TreeMap<PropertyName, ArrayList<GroupBinding>> bindings = new TreeMap<>(); + + Map<PropertyName, FlexibleListProperty> properties = group.getChildPropertyStructure().getProperties(); + + for(int i = 0; i < jsonArray.size(); ++i) { + JsonArray bindingArray = jsonArray.getJsonArray(i); + ArrayList<Property> boundProps = new ArrayList<>(); + + FlexibleListProperty propList = null; + for(int k = 0; k < bindingArray.size(); ++k) { + PropertyName name = new PropertyName(bindingArray.getString(k)); + if(propList == null) propList = properties.get(name); + Property property = propList.getByName(name); + if(property!=null) boundProps.add(property); + } + + PropertyName name = new PropertyName(new PropertyName(boundProps.get(0).getName()).getNoPostfixName()); + ArrayList<GroupBinding> binding = bindings.get(name); + if(binding == null) { + binding = new ArrayList<>(); + bindings.put(name, binding); + } + binding.add(new GroupBinding(boundProps)); + } + + return bindings; + } +} + diff --git a/src/fr/inria/structgraphics/persistence/JsonObjectWriter.java b/src/fr/inria/structgraphics/persistence/JsonObjectWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..2114efa9873ce278268e289fa41e8a3551ca84a1 --- /dev/null +++ b/src/fr/inria/structgraphics/persistence/JsonObjectWriter.java @@ -0,0 +1,255 @@ +package fr.inria.structgraphics.persistence; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; +import java.util.SortedSet; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.json.JsonValue.ValueType; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import fr.inria.structgraphics.ui.viscanvas.groupings.GroupPropertyStructure; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleDoubleProperty; + +public class JsonObjectWriter { + + public static JsonObject saveToJason(Collection<Mark> marks, boolean isLibrary) { + return buildJason(marks, isLibrary).build(); + } + + public static JsonObjectBuilder buildJason(Collection<Mark> marks, boolean isLibrary, JsonObjectBuilder visbuilder) { + JsonArrayBuilder builder = Json.createArrayBuilder(); + for(Mark mark: marks) { + save(builder, mark, mark); + } + + visbuilder.add(isLibrary? "library" : "visualizations", builder); + + return visbuilder; + } + + public static JsonObjectBuilder buildJason(Collection<Mark> marks, boolean isLibrary) { + JsonObjectBuilder visbuilder = Json.createObjectBuilder(); + return buildJason(marks, isLibrary, visbuilder); + } + + private static JsonArrayBuilder save(JsonArrayBuilder parentBuilder, Mark mark, Mark root) { + JsonObjectBuilder builder = Json.createObjectBuilder(); + builder.add("type", mark.getType()); + if(mark != root) { // Since the ID of the root node may change when re-loaded to the canvas + builder.add("id", getRelativeId(root.id.get(), mark.id.get())); + } + builder.add("level", mark.getLevel()); + + if(mark instanceof VisBody) { + builder.add("prefix", PropertyName.getPrefix(mark.getLevel())); + } + + List<Property> properties = mark.getProperties(); + for(Property prop: properties) saveProperty(builder, prop); + + if(mark instanceof VisCollection) { + // Save the property structure + CollectionPropertyStructure structure = ((VisCollection)mark).getChildPropertyStructure(); + SortedSet<PropertyName> common = structure.getCommon(); + JsonArrayBuilder commonBuilder = Json.createArrayBuilder(); + for(PropertyName name: common) commonBuilder.add(name.toString()); + builder.add("common", commonBuilder.build()); + + SortedSet<PropertyName> variable = structure.getVariable(); + JsonArrayBuilder variableBuilder = Json.createArrayBuilder(); + for(PropertyName name: variable) variableBuilder.add(name.toString()); + builder.add("variable", variableBuilder.build()); + } else if(mark instanceof VisGroup) { + //////////// + GroupPropertyStructure structure = ((VisGroup)mark).getChildPropertyStructure(); + SortedSet<PropertyName> common = structure.getCommon(); + JsonArrayBuilder commonBuilder = Json.createArrayBuilder(); + for(PropertyName name: common) commonBuilder.add(name.toString()); + builder.add("common", commonBuilder.build()); + + SortedSet<PropertyName> variable = structure.getVariable(); + JsonArrayBuilder variableBuilder = Json.createArrayBuilder(); + for(PropertyName name: variable) variableBuilder.add(name.toString()); + builder.add("variable", variableBuilder.build()); + + if(((VisGroup)mark).isTopGroup()) { + JsonArrayBuilder publicBuilder = Json.createArrayBuilder(); + for(Property prop: ((VisGroup)mark).getAllProperties()) { + if(prop instanceof Shareable && ((Shareable)prop).getPublicProperty().get()) + publicBuilder.add(prop.getName()); + } + builder.add("public", publicBuilder.build()); + + JsonArrayBuilder bindingsBuilder = Json.createArrayBuilder(); + + for(PropertyName propName: structure.getProperties().keySet()) { + + ArrayList<GroupBinding> bindings = structure.getGroups(propName); + if(bindings == null) continue; + for(GroupBinding binding: bindings) { + JsonArrayBuilder propBuilder = Json.createArrayBuilder(); + + ArrayList boundproperties = binding.getProperties(); + for(int i = 0; i < boundproperties.size(); ++i) { + propBuilder.add(((Property)boundproperties.get(i)).getName()); + } + + bindingsBuilder.add(propBuilder); + } + } + + builder.add("bindings", bindingsBuilder.build()); + } + } else saveProperty(builder, mark.ratiolock); + + List<Mark> components = mark.getComponents(); + if(!components.isEmpty()) { + JsonArrayBuilder childrenBuilder = Json.createArrayBuilder(); + for(Mark child: components) { + save(childrenBuilder, child, root); + } + + builder.add("components", childrenBuilder); + } + + + if(mark instanceof LineConnectedCollection && ((LineConnectedCollection)mark).hasConnections()) { + LineConnectedCollection collection = ((LineConnectedCollection)mark); + builder.add(new PropertyName(collection.flowPaint.getName()).getCleanName(), collection.flowPaint.get().toString()); + builder.add(new PropertyName(collection.flowOpacity.getName()).getCleanName(), collection.flowOpacity.get()); + builder.add(new PropertyName(collection.coloringScheme.getName()).getCleanName(), collection.coloringScheme.get().toString()); + + JsonArrayBuilder connectionsArrayBuilder = Json.createArrayBuilder(); + // Save the flow connections structure + for(FlowConnection conn : collection.getFlowConnections()) { + JsonObjectBuilder connectionBuilder = Json.createObjectBuilder(); + connectionBuilder.add("origin", getRelativeId(root.id.get(), conn.getOrigin().id.get())); + connectionBuilder.add("destination", getRelativeId(root.id.get(), conn.getDestination().id.get())); + connectionBuilder.add("weight", conn.weight.get()); + + connectionsArrayBuilder.add(connectionBuilder); + } + + builder.add("connections", connectionsArrayBuilder); + } + + parentBuilder.add(builder); + return parentBuilder; + } + + private static void saveProperty(JsonObjectBuilder builder, Property property) { + PropertyName propName = new PropertyName(property.getName()); + + if(property instanceof SimpleDoubleProperty) + builder.add(propName.getCleanName(), ((SimpleDoubleProperty)property).doubleValue()); + else builder.add(propName.getCleanName(), property.getValue().toString()); + } + + + public static StringBuffer fromArrayToReadableString(JsonArray array, int level) { + StringBuffer buffer = new StringBuffer(); + if(array.isEmpty()) { + buffer.append("[]"); + return buffer; + } + + if(array.get(0).getValueType() == ValueType.STRING) { + buffer.append("["); + for(int i = 0; i < array.size(); ++i) { + JsonString value = array.getJsonString(i); + buffer.append(value); + buffer.append(","); + } + buffer.deleteCharAt(buffer.length() - 1); + buffer.append("]"); + } + else if(array.get(0).getValueType() == ValueType.ARRAY) { + buffer.append("[\n"); + + for(int i = 0; i < array.size(); ++i) { + JsonArray visObject = array.getJsonArray(i); + buffer.append(tabs(level+1)); + buffer.append(fromArrayToReadableString(visObject, (level + 1))); + buffer.append(",\n"); + } + + buffer.deleteCharAt(buffer.length() - 2); + buffer.append(tabs(level) + "]"); + } + else { + buffer.append("[\n"); + + for(int i = 0; i < array.size(); ++i) { + JsonObject visObject = array.getJsonObject(i); + buffer.append(fromObjectToReadableString(visObject, (level + 1))); + buffer.append(",\n"); + } + + buffer.deleteCharAt(buffer.length() - 2); + buffer.append(tabs(level) + "]"); + } + + + return buffer; + } + + private static String getRelativeId(String rootId, String id) { + return id.substring(rootId.length() + 1); + } + + public static StringBuffer fromObjectToReadableString(JsonObject visObject, int level) { + StringBuffer buffer = new StringBuffer(); + + buffer.append(tabs(level) + "{\n"); + + for(Entry<String, JsonValue> entry: visObject.entrySet()) { + buffer.append(tabs(level + 1)); + buffer.append("\"" + entry.getKey() + "\""); + buffer.append(":"); + + JsonValue value = entry.getValue(); + if(value instanceof JsonArray) { + buffer.append(fromArrayToReadableString((JsonArray)value, (level + 2))); + } else if(value instanceof JsonObject) { + buffer.append(fromObjectToReadableString((JsonObject)value, (level + 2))); + } + else buffer.append(entry.getValue()); + + buffer.append(",\n"); + } + + buffer.deleteCharAt(buffer.length() - 2); + + buffer.append(tabs(level) + "}"); + + return buffer; + } + + private static StringBuffer tabs(int num) { + StringBuffer str = new StringBuffer(); + for(int i = 0; i < num; ++i) str.append(" "); + + return str; + } + +} + diff --git a/src/fr/inria/structgraphics/persistence/JsonSpreadsheetWriterReader.java b/src/fr/inria/structgraphics/persistence/JsonSpreadsheetWriterReader.java new file mode 100644 index 0000000000000000000000000000000000000000..6d737a61ea1018b61f3af0ffe6e909587c4ad133 --- /dev/null +++ b/src/fr/inria/structgraphics/persistence/JsonSpreadsheetWriterReader.java @@ -0,0 +1,211 @@ +package fr.inria.structgraphics.persistence; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonString; +import javax.json.JsonValue; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.PositionCoords; +import fr.inria.structgraphics.graphics.ReferenceCoords; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.Draggable.Type; +import fr.inria.structgraphics.ui.spreadsheet.AreaContent; +import fr.inria.structgraphics.ui.spreadsheet.AreaSourceInfo; +import fr.inria.structgraphics.ui.spreadsheet.ColumnVariable; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.spreadsheet.DiscreteTransformation; +import fr.inria.structgraphics.ui.spreadsheet.MappingProperty; +import fr.inria.structgraphics.ui.spreadsheet.MathTransformation; +import fr.inria.structgraphics.ui.spreadsheet.Spreadsheet; +import fr.inria.structgraphics.ui.spreadsheet.SpreadsheetArea; +import fr.inria.structgraphics.ui.spreadsheet.VectorVariable; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable.DataType; +import javafx.beans.property.StringProperty; +import javafx.scene.paint.Paint; + +public class JsonSpreadsheetWriterReader { + + public static JsonObjectBuilder buildJason(Spreadsheet sheet, JsonObjectBuilder visbuilder) { + List<SpreadsheetArea> areas = sheet.getAreas(); + JsonArrayBuilder builder = Json.createArrayBuilder(); + for(SpreadsheetArea area: areas) { + save(builder, area); + } + + visbuilder.add("datasheet", builder); + + return visbuilder; + } + + public static JsonObjectBuilder buildJason(Spreadsheet sheet) { + JsonObjectBuilder visbuilder = Json.createObjectBuilder(); + return buildJason(sheet, visbuilder); + } + + private static JsonArrayBuilder save(JsonArrayBuilder parentBuilder, SpreadsheetArea area) { + JsonObjectBuilder builder = Json.createObjectBuilder(); + + builder.add("type", area.getType().toString()); + + builder.add("row", area.getStartRow()); + builder.add("column", area.getStartColumn()); + builder.add("nrows", area.nrows()); + builder.add("ncolumns", area.ncolumns()); + + builder.add("source", area.getSource().id.get()); + if(area.getVisBody() != null) builder.add("group", area.getVisBody().id.get()); + builder.add("wide", area.isWide()); + builder.add("network", area.isNetwork()); + + ArrayList<PropertyName> names = area.getPropertyNames(); + JsonArrayBuilder namesArray = Json.createArrayBuilder(); + for(PropertyName name: names) { + namesArray.add(name.toString()); + } + builder.add("properties", namesArray); + + JsonArrayBuilder variablesArray = Json.createArrayBuilder(); + // TODO: To add the variable mappings + AreaContent areaContent = area.getAreaContent(); + for(DataVariable variable : areaContent.getVariables()) { + JsonObjectBuilder varObject = Json.createObjectBuilder(); + + varObject.add("name", variable.getPropertyName()); + varObject.add("type", variable.getType().toString()); + + // Labels, axes, legends + varObject.add("axis", variable.scaleShownProperty.get()); + varObject.add("axis-outer", variable.scaleShownPropertyOuter.get()); + varObject.add("legend", variable.legendShownProperty.get()); + varObject.add("node", variable.nodeShownProperty.get()); + + JsonArrayBuilder labelsArray = Json.createArrayBuilder(); + for(String label: variable.getNames()) { + labelsArray.add(label); + } + varObject.add("labels", labelsArray); + + /// Add the transformation + if(variable.getType() == DataType.Functional) { + varObject.add("function", variable.transformationProperty().get().getExpression().getValue()); + } else { + DiscreteTransformation transform = (DiscreteTransformation) variable.transformationProperty().get(); + TreeMap<MappingProperty, StringProperty> map = transform.getMap(); + JsonArrayBuilder mappingsArray = Json.createArrayBuilder(); + + for(MappingProperty prop: map.keySet()) { + JsonObjectBuilder mappingObject = Json.createObjectBuilder(); + StringProperty val = map.get(prop); + mappingObject.add("from", prop.getValue().toString()); + mappingObject.add("to", val.get()); + mappingsArray.add(mappingObject); + } + + varObject.add("mappings", mappingsArray); + } + /// + variablesArray.add(varObject ); + } + builder.add("variables", variablesArray); + + parentBuilder.add(builder); + return parentBuilder; + } + + + public static void readSpreadsheetFromJasonObject(JsonArray jsonArray, VisFrame visFrame, Spreadsheet sheet) { + for(int i = 0; i < jsonArray.size(); ++i) { + JsonValue jsonObject = jsonArray.get(i); + + if(jsonObject instanceof JsonObject) { + readAreaFromJasonObject((JsonObject)jsonObject, visFrame, sheet); + } + } + } + + private static void readAreaFromJasonObject(JsonObject jsonObject, VisFrame visFrame, Spreadsheet sheet) { + Type type = Draggable.Type.valueOf(jsonObject.getString("type")); + int row = jsonObject.getInt("row"); + int column = jsonObject.getInt("column"); + int nrows = jsonObject.getInt("nrows"); + int ncolumns = jsonObject.getInt("ncolumns"); + + boolean isWide = jsonObject.getBoolean("wide"); + boolean isNetwork = jsonObject.getBoolean("network"); + + Container source = visFrame.getComponent(jsonObject.getString("source")); + JsonString groupID = jsonObject.getJsonString("group"); + VisBody visBody = (groupID == null) ? null : (VisBody)visFrame.getComponent(groupID.toString()); + + JsonArray propertiesArray = jsonObject.getJsonArray("properties"); + ArrayList<PropertyName> propertyNames = new ArrayList<>(); + for(int i = 0; i < propertiesArray.size(); ++i) { + PropertyName name = new PropertyName(propertiesArray.getJsonString(i).getString()); + propertyNames.add(name); + } + + AreaSourceInfo info = new AreaSourceInfo(source, visBody, propertyNames, type, isWide, isNetwork); + SpreadsheetArea area = new SpreadsheetArea(sheet, row, column, ncolumns, nrows); + area.setInfo(info); + sheet.addArea(area); + + AreaContent content = area.getAreaContent(); + + /// Variables + JsonArray variablesArray = jsonObject.getJsonArray("variables"); + for(int i = 0; i < variablesArray.size(); ++i) { + JsonObject varObject = variablesArray.getJsonObject(i); + + String varName = varObject.getString("name"); + DataVariable variable = content.getVariable(varName); + + DataType datatype = DataType.valueOf(varObject.getString("type")); + JsonArray labelsArray = varObject.getJsonArray("labels"); + for(int k = 0; k < labelsArray.size(); ++k) { + String label = labelsArray.getString(k); + variable.getName(k).set(label); + } + + if(datatype == DataType.Functional) { + String expression = varObject.getString("function"); + MathTransformation mathTransform = new MathTransformation(variable); + mathTransform.setExpression(expression); + variable.transformationProperty().set(mathTransform); + } else { + DiscreteTransformation discreteTransform = new DiscreteTransformation(variable); + variable.transformationProperty().set(discreteTransform); + + JsonArray mappingsArray = varObject.getJsonArray("mappings"); + for(int k = 0; k < mappingsArray.size(); ++k) { + JsonObject mapping = mappingsArray.getJsonObject(k); + discreteTransform.replace(mapping.getString("from"), mapping.getString("to")); + } + } + + // Labels, axes, legends + if(varObject.getBoolean("axis")) sheet.showOnGraph(variable); + if(varObject.getBoolean("axis-outer")) sheet.showOnParentGraph(variable); + if(varObject.getBoolean("legend")) sheet.showOnLegend(variable); + if(varObject.getBoolean("node")) sheet.showOnNode(variable); + } + + } +} + + + diff --git a/src/fr/inria/structgraphics/persistence/JsonWorkspaceWriter.java b/src/fr/inria/structgraphics/persistence/JsonWorkspaceWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..d471bf4f4a9dfa9be31e5b7aaa04b4c53381fb97 --- /dev/null +++ b/src/fr/inria/structgraphics/persistence/JsonWorkspaceWriter.java @@ -0,0 +1,27 @@ +package fr.inria.structgraphics.persistence; + +import java.util.Collection; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.ui.spreadsheet.Spreadsheet; + +public class JsonWorkspaceWriter { + + public static JsonObject saveToJason(Collection<Mark> librarymarks, Collection<Mark> canvasmarks, Spreadsheet sheet) { + JsonObjectBuilder rootBuilder = Json.createObjectBuilder(); + + JsonObjectBuilder spaceBuilder = JsonObjectWriter.buildJason(librarymarks, true); + JsonObjectWriter.buildJason(canvasmarks, false, spaceBuilder); + JsonSpreadsheetWriterReader.buildJason(sheet, spaceBuilder); + + rootBuilder.add("workspace", spaceBuilder); + + return rootBuilder.build(); + } + +} + diff --git a/src/fr/inria/structgraphics/types/AlignmentProperty.java b/src/fr/inria/structgraphics/types/AlignmentProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..159cb643597f85c4e44ea87429ebe68d0d461eaf --- /dev/null +++ b/src/fr/inria/structgraphics/types/AlignmentProperty.java @@ -0,0 +1,60 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class AlignmentProperty<T> extends SimpleObjectProperty<T> implements Shareable { + + public enum YSticky { + No, Yes + } + + public enum XSticky { + No, Yes + } + + public AlignmentProperty(Object bean, String name, T value) { + super(bean, name, value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + protected BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof AlignmentProperty) { + return getValue().equals(((AlignmentProperty) property).getValue()); + } + + return false; + } + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/AlignmentXProperty.java b/src/fr/inria/structgraphics/types/AlignmentXProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..664b6a0b6130ce2f18e374059b878bf377c3a4a2 --- /dev/null +++ b/src/fr/inria/structgraphics/types/AlignmentXProperty.java @@ -0,0 +1,24 @@ +package fr.inria.structgraphics.types; + +public class AlignmentXProperty extends AlignmentProperty<AlignmentProperty.YSticky> { + + public AlignmentXProperty(Object bean) { + super(bean, "sticky-y", YSticky.No); + } + + public AlignmentXProperty(Object bean, YSticky value) { + super(bean, "sticky-y", value); + } + + /* + public String getIconName() { + return "x-" + getValue().toString().toLowerCase(); + }*/ + + @Override + public void setValue(AlignmentProperty.YSticky value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + +} diff --git a/src/fr/inria/structgraphics/types/AlignmentYProperty.java b/src/fr/inria/structgraphics/types/AlignmentYProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..81adb061ce726665932434ae3eab56b319fcf932 --- /dev/null +++ b/src/fr/inria/structgraphics/types/AlignmentYProperty.java @@ -0,0 +1,28 @@ +package fr.inria.structgraphics.types; + +import javafx.beans.property.Property; + +public class AlignmentYProperty extends AlignmentProperty<AlignmentProperty.XSticky> { + + static int counter = 0; + + public AlignmentYProperty(Object bean) { + super(bean, "sticky-x", XSticky.No); + } + + public AlignmentYProperty(Object bean, XSticky value) { + super(bean, "sticky-x", value); + } + + /* + public String getIconName() { + return "y-" + getValue().toString().toLowerCase(); + }*/ + + @Override + public void setValue(AlignmentProperty.XSticky value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + +} diff --git a/src/fr/inria/structgraphics/types/AngleProperty.java b/src/fr/inria/structgraphics/types/AngleProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..f7041476aa5241830e9778da8a1fb9615f7878c3 --- /dev/null +++ b/src/fr/inria/structgraphics/types/AngleProperty.java @@ -0,0 +1,60 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class AngleProperty extends SimpleDoubleProperty implements Shareable { + + protected final static double EPSILON = 5; + + public AngleProperty(Object bean, String name, double val) { + super(bean, name, val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Number value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof AngleProperty) { + if(Math.abs(((AngleProperty) property).get() - get()) < EPSILON) return true; + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/CardinalityProperty.java b/src/fr/inria/structgraphics/types/CardinalityProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..ceef14cf24027874f285140948bca9889dc48af0 --- /dev/null +++ b/src/fr/inria/structgraphics/types/CardinalityProperty.java @@ -0,0 +1,59 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; + +public class CardinalityProperty extends SimpleIntegerProperty implements Shareable{ + + + public CardinalityProperty(Object bean) { + super(bean, "cardinality"); + } + + public CardinalityProperty(Object bean, String name, int value) { + super(bean, name, value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof CardinalityProperty) { + return getValue().equals(((CardinalityProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/ColorListProperty.java b/src/fr/inria/structgraphics/types/ColorListProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..e0515f3cf84c5094853f7e78cfb3a6728116053b --- /dev/null +++ b/src/fr/inria/structgraphics/types/ColorListProperty.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.types; + +import java.util.ArrayList; +import javafx.collections.FXCollections; + +public class ColorListProperty extends FlexibleListProperty{ + + public ColorListProperty(Object bean, ColorProperty property) { + super(bean, property.getName(), FXCollections.observableArrayList(new ArrayList<ColorProperty>())); + add(property); + } + +} diff --git a/src/fr/inria/structgraphics/types/ColorProperty.java b/src/fr/inria/structgraphics/types/ColorProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..b9a655393c07013c4bf11d0596be7226f2c51240 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ColorProperty.java @@ -0,0 +1,59 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.paint.Paint; + +public class ColorProperty extends SimpleObjectProperty<Paint> implements Shareable { + public ColorProperty(java.lang.Object bean, String name, Paint color) { + super(bean, name, color); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Paint value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof ColorProperty) { + return getValue().equals(((ColorProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/ColoringSchemeProperty.java b/src/fr/inria/structgraphics/types/ColoringSchemeProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..c66dd469cd7397ae51812213cf51fba0768cbfd4 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ColoringSchemeProperty.java @@ -0,0 +1,62 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class ColoringSchemeProperty extends SimpleObjectProperty<ColoringSchemeProperty.Scheme> implements Shareable { + + public enum Scheme { + Common, Source, Destination + } + + public ColoringSchemeProperty(Object bean, ColoringSchemeProperty.Scheme value) { + super(bean, "coloring", value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(ColoringSchemeProperty.Scheme value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof ColoringSchemeProperty) { + return getValue().equals(((ColoringSchemeProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/ComponentRefProperty.java b/src/fr/inria/structgraphics/types/ComponentRefProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..5f3560add5e2e3a0ae668c40aa55333d004a769d --- /dev/null +++ b/src/fr/inria/structgraphics/types/ComponentRefProperty.java @@ -0,0 +1,11 @@ +package fr.inria.structgraphics.types; + + +public class ComponentRefProperty<T> extends RefProperty<T> { + + public ComponentRefProperty(Object bean, String name, T value) { + super(bean, name, value); + } + +} + diff --git a/src/fr/inria/structgraphics/types/ComponentRefXProperty.java b/src/fr/inria/structgraphics/types/ComponentRefXProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..0125d0f07fa7e8c777f51491c8f1ee21ea62541f --- /dev/null +++ b/src/fr/inria/structgraphics/types/ComponentRefXProperty.java @@ -0,0 +1,21 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; + +public class ComponentRefXProperty extends ComponentRefProperty<RefX> { + + public ComponentRefXProperty(Object bean) { + super(bean, "reference-x", RefX.Left); + } + + public ComponentRefXProperty(Object bean, RefX value) { + super(bean, "reference-x", value); + } + + + @Override + public void setValue(RefX value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } +} diff --git a/src/fr/inria/structgraphics/types/ComponentRefYProperty.java b/src/fr/inria/structgraphics/types/ComponentRefYProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..7c079dd865e041070bdad2ff1ea962ad64275b48 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ComponentRefYProperty.java @@ -0,0 +1,20 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; + +public class ComponentRefYProperty extends ComponentRefProperty<RefY> { + + public ComponentRefYProperty(Object bean) { + super(bean, "reference-y", RefY.Bottom); + } + + public ComponentRefYProperty(Object bean, RefY value) { + super(bean, "reference-y", value); + } + + @Override + public void setValue(RefY value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } +} diff --git a/src/fr/inria/structgraphics/types/ConnectionWeightProperty.java b/src/fr/inria/structgraphics/types/ConnectionWeightProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..2a3b19c335c9b59096851fec20433cac12d5cea9 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ConnectionWeightProperty.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.types; + +public class ConnectionWeightProperty extends DistanceProperty { + + public ConnectionWeightProperty(java.lang.Object bean) { + super(bean, "weight", 0); + } + + public ConnectionWeightProperty(java.lang.Object bean, double value) { + super(bean, "weight", value); + } + +} diff --git a/src/fr/inria/structgraphics/types/ConstrainedDoubleProperty.java b/src/fr/inria/structgraphics/types/ConstrainedDoubleProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..40696b9eb202036b9be54448112dc43578fc2d73 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ConstrainedDoubleProperty.java @@ -0,0 +1,80 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class ConstrainedDoubleProperty extends SimpleDoubleProperty implements Shareable { + + protected final static double EPSILON = 5; + + public ConstrainedDoubleProperty(Object bean, String name, double val) { + super(bean, name, val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Number value) { + if(!value.equals(get())) super.setValue(value); + else { + fireValueChangedEvent(); + } + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + + public void updateValue(double value) { + this.setValue(value); + + if(getBean() instanceof Mark) { + Container container = ((Mark)getBean()).getContainer(); + if(container instanceof VisBody) { + ((VisBody)container).updateLayout((Mark)getBean()); + } + } else if(getBean() instanceof FlowConnection) { + Container container = ((FlowConnection)getBean()).getOrigin().getContainer(); + if(container instanceof VisBody) { + ((VisBody)container).updateLayout(((FlowConnection)getBean()).getOrigin()); + } + } + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof ConstrainedDoubleProperty) { + if(Math.abs(((ConstrainedDoubleProperty) property).get() - get()) < EPSILON) return true; + } + + return false; + } + +} diff --git a/src/fr/inria/structgraphics/types/ContainerRefProperty.java b/src/fr/inria/structgraphics/types/ContainerRefProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..aede7fde51745471217bb9d235e75ba4577f63c5 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ContainerRefProperty.java @@ -0,0 +1,9 @@ +package fr.inria.structgraphics.types; + +public class ContainerRefProperty<T> extends RefProperty<T> { + + public ContainerRefProperty(Object bean, String name, T value) { + super(bean, name, value); + } + +} diff --git a/src/fr/inria/structgraphics/types/ContainerRefXProperty.java b/src/fr/inria/structgraphics/types/ContainerRefXProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..02b3734b226718cc06da6896c06c4b2dbae98a57 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ContainerRefXProperty.java @@ -0,0 +1,21 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; + +public class ContainerRefXProperty extends ContainerRefProperty<RefX> { + + public ContainerRefXProperty(Object bean) { + super(bean, "reference-x", RefX.Left); + } + + public ContainerRefXProperty(Object bean, RefX value) { + super(bean, "reference-x", value); + } + + @Override + public void setValue(RefX value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + +} diff --git a/src/fr/inria/structgraphics/types/ContainerRefYProperty.java b/src/fr/inria/structgraphics/types/ContainerRefYProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..83ad96ba52957073cf89a4406d3cdafb65808a31 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ContainerRefYProperty.java @@ -0,0 +1,20 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; + +public class ContainerRefYProperty extends ContainerRefProperty<RefY> { + + public ContainerRefYProperty(Object bean) { + super(bean, "reference-y", RefY.Bottom); + } + + public ContainerRefYProperty(Object bean, RefY value) { + super(bean, "reference-y", value); + } + + @Override + public void setValue(RefY value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } +} diff --git a/src/fr/inria/structgraphics/types/CoordinateProperty.java b/src/fr/inria/structgraphics/types/CoordinateProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..723be49e900f0ec1b3b69f2f83a6292466db5293 --- /dev/null +++ b/src/fr/inria/structgraphics/types/CoordinateProperty.java @@ -0,0 +1,8 @@ +package fr.inria.structgraphics.types; + +public class CoordinateProperty extends ConstrainedDoubleProperty { + + public CoordinateProperty(Object bean, String name, double val) { + super(bean, name, val); + } +} diff --git a/src/fr/inria/structgraphics/types/DeltaXProperty.java b/src/fr/inria/structgraphics/types/DeltaXProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..79283016b47ce8f365f563a209578c9c3a537b02 --- /dev/null +++ b/src/fr/inria/structgraphics/types/DeltaXProperty.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.types; + +public class DeltaXProperty extends DistanceProperty { + + public DeltaXProperty(java.lang.Object bean) { + super(bean, "delta-x", 0); + } + + public DeltaXProperty(java.lang.Object bean, double value) { + super(bean, "delta-x", value); + } + +} diff --git a/src/fr/inria/structgraphics/types/DeltaYProperty.java b/src/fr/inria/structgraphics/types/DeltaYProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..2ba7c87f9f77e20784850df94476a3fbca4344ae --- /dev/null +++ b/src/fr/inria/structgraphics/types/DeltaYProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class DeltaYProperty extends DistanceProperty { + + public DeltaYProperty(java.lang.Object bean) { + super(bean, "delta-y", 0); + } + + public DeltaYProperty(java.lang.Object bean, double value) { + super(bean, "delta-y", value); + } +} diff --git a/src/fr/inria/structgraphics/types/DistanceProperty.java b/src/fr/inria/structgraphics/types/DistanceProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..17bb6fd83cea89e68c43331baa4a5ff7a6191d71 --- /dev/null +++ b/src/fr/inria/structgraphics/types/DistanceProperty.java @@ -0,0 +1,9 @@ +package fr.inria.structgraphics.types; + +public class DistanceProperty extends ConstrainedDoubleProperty { + + public DistanceProperty(Object bean, String name, double val) { + super(bean, name, val); + } + +} diff --git a/src/fr/inria/structgraphics/types/DistributionProperty.java b/src/fr/inria/structgraphics/types/DistributionProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..89e1fd0dc622366b508543cfdf516d45b91dfd38 --- /dev/null +++ b/src/fr/inria/structgraphics/types/DistributionProperty.java @@ -0,0 +1,62 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class DistributionProperty extends SimpleObjectProperty<DistributionProperty.Constraint> implements Shareable { + + public enum Constraint { + None, Spacing, Distance + } + + public DistributionProperty(Object bean, String name, DistributionProperty.Constraint value) { + super(bean, name, value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(DistributionProperty.Constraint value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof DistributionProperty) { + return getValue().equals(((DistributionProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/DistributionXProperty.java b/src/fr/inria/structgraphics/types/DistributionXProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..9a2b4ea0cb1bd201d61ce84f71d8de6082648dff --- /dev/null +++ b/src/fr/inria/structgraphics/types/DistributionXProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class DistributionXProperty extends DistributionProperty { + + public DistributionXProperty(Object bean) { + super(bean, "distribution-x", Constraint.None); + } + + public DistributionXProperty(Object bean, Constraint value) { + super(bean, "distribution-x", value); + } +} diff --git a/src/fr/inria/structgraphics/types/DistributionYProperty.java b/src/fr/inria/structgraphics/types/DistributionYProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..371fe8b1ae2122b8cd309777b6f95c5f45bb99f9 --- /dev/null +++ b/src/fr/inria/structgraphics/types/DistributionYProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class DistributionYProperty extends DistributionProperty { + + public DistributionYProperty(Object bean) { + super(bean, "distribution-y", Constraint.None); + } + + public DistributionYProperty(Object bean, Constraint value) { + super(bean, "distribution-y", value); + } +} diff --git a/src/fr/inria/structgraphics/types/DoubleListProperty.java b/src/fr/inria/structgraphics/types/DoubleListProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..ef3734b5743d0cee6d4561e40f4ac88b21e547c4 --- /dev/null +++ b/src/fr/inria/structgraphics/types/DoubleListProperty.java @@ -0,0 +1,37 @@ +package fr.inria.structgraphics.types; + +import java.util.ArrayList; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.Property; +import javafx.collections.FXCollections; + +public class DoubleListProperty extends FlexibleListProperty { + + protected DoubleListProperty(Object bean, DoubleProperty property) { + super(bean, property.getName(), FXCollections.observableArrayList(new ArrayList<DoubleProperty>())); + add(property); + } + + public DoubleListProperty(Object bean, String name) { + super(bean, name, FXCollections.observableArrayList(new ArrayList<DoubleProperty>())); + } + + public double average() { + double sum = 0; + for(Property property:get()) { + sum += ((DoubleProperty)property).doubleValue(); + } + + return sum/size(); + } + + public double variance(double mean) { + double sum = 0; + + for(Property property:get()) { + sum += Math.pow(((DoubleProperty)property).doubleValue() - mean, 2); + } + + return sum/size(); + } +} diff --git a/src/fr/inria/structgraphics/types/FillColorProperty.java b/src/fr/inria/structgraphics/types/FillColorProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..7c77574bae901be3813cbc2062e227a750b4b1ba --- /dev/null +++ b/src/fr/inria/structgraphics/types/FillColorProperty.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.types; + +import javafx.scene.paint.Color; + +public class FillColorProperty extends ColorProperty { + + public FillColorProperty(java.lang.Object bean) { + super(bean, "fill", Color.WHITE); + } + + public FillColorProperty(java.lang.Object bean, Color col) { + super(bean, "fill", col); + } +} diff --git a/src/fr/inria/structgraphics/types/FlexibleListProperty.java b/src/fr/inria/structgraphics/types/FlexibleListProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..e2b66988cddc643554bc999cb81e21c866284f4c --- /dev/null +++ b/src/fr/inria/structgraphics/types/FlexibleListProperty.java @@ -0,0 +1,440 @@ +package fr.inria.structgraphics.types; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.SortedSet; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.ui.inspector.util.PropertyCloner; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.ObservableList; + +public class FlexibleListProperty extends SimpleListProperty<Property> implements Shareable { + + private boolean protectFullName = false; + + protected FlexibleListProperty(Object bean, String name, ObservableList<Property> observableArrayList) { + super(bean, name, observableArrayList); + } + + public static FlexibleListProperty createList(Object bean, Property property, boolean protectName) { + FlexibleListProperty list = createList(bean, property); + if(protectName) list.protectFullName = true; + return list; + } + + public static FlexibleListProperty createList(Object bean, List<Property> properties, boolean protectName) { + FlexibleListProperty list = createList(bean, properties); + if(protectName) list.protectFullName = true; + return list; + } + + + public static FlexibleListProperty createList(Object bean, Property property) { + FlexibleListProperty listProperty = null; + + if(property instanceof DoubleProperty) { + listProperty = new DoubleListProperty(bean, (DoubleProperty)property); + } else if(property instanceof RefProperty) { + listProperty = new ObjectListProperty(bean, (RefProperty)property); + } else if(property instanceof ColorProperty) { + listProperty = new ColorListProperty(bean, (ColorProperty)property); + } else if(property instanceof AlignmentProperty) { + listProperty = new ObjectListProperty(bean, (AlignmentProperty)property); + } else if(property instanceof ShapeProperty) { + listProperty = new ObjectListProperty(bean, (ShapeProperty)property); + } else if (property instanceof LineTypeProperty) { + listProperty = new ObjectListProperty(bean, (LineTypeProperty)property); + } else if(property instanceof DistributionProperty) { + listProperty = new ObjectListProperty(bean, (DistributionProperty)property); + } + else if(property instanceof TextProperty) { + listProperty = new StringListProperty(bean, (TextProperty)property); + } + else if(property instanceof FlexibleListProperty) { + listProperty = new NestedListProperty(bean, (FlexibleListProperty)property); + } else if(property instanceof StringProperty) { + listProperty = new StringListProperty(bean, (StringProperty)property); + } + + return listProperty; + } + + + public static FlexibleListProperty createList(Object bean, List<Property> properties) { + if(properties.isEmpty()) return null; + + FlexibleListProperty listProperty = createList(bean, properties.get(0)); + for(int i=1; i<properties.size(); ++i) + listProperty.add(properties.get(i)); + + return listProperty; + } + + /* + * This was added to fix reassure that properties within groups appear with full name when shown within collections + */ + + @Override + public String getName() { + //System.err.println(super.getName() + " " + getBean()); + if(!protectFullName && getBean() instanceof VisGroup) return new PropertyName(super.getName()).getNoPostfixName(); + else return super.getName(); + } + + public void addEach(List<Property> properties) { + for(Property prop: properties) + add(prop); + } + + public void addEach(int index, List<Property> properties) { + for(Property prop: properties) + add(index, prop); + } + + public boolean allEqual() { + if(isEmpty()) return true; + + Object val = get(0).getValue(); + for(int i = 1; i < size(); ++i ) { + if(!get(i).getValue().equals(val)) return false; + } + + return true; + } + + public Object firstValue() { + if(isEmpty()) return null; + else return get(0).getValue(); + } + + public Object mode() { + HashMap<Object, Integer> map = new HashMap<>(); + + // The integer value of the map will contain the number of occurences of the property value + for(Property property:get()) { + Object val = property.getValue(); + if(map.containsKey(val)) + map.put(val, map.get(val) + 1); + else map.put(val, 1); + } + + int max = 0; + Object val = null; + for(Object key: map.keySet()) { + int count = map.get(key); + if(count > max) val = key; + } + + return val; + } + + public PropertyName getPropertyName() { + return new PropertyName(getName()); + } + + @Override + public Object getBean() { + Object object = super.getBean(); + if(object instanceof Mark) { + Mark mark = (Mark)object; + if(mark.getContainer() instanceof VisGroup) { + return mark.getTopGroup(); + } + } + + return super.getBean(); + } + + /* + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPrefix(super.getName()); // TODO + } else return super.getName(); + }*/ + + + public void bindElements(FlexibleListProperty property) { + for(int i = 0; i < size() && i < property.size(); ++i) { + Property prop1 = get(i); + Property prop2 = property.get(i); + + if(prop1 instanceof FlexibleListProperty) ((FlexibleListProperty)prop1).bindElements((FlexibleListProperty)prop2); + else prop1.bindBidirectional(prop2); + } + } + + public void unbindElements(FlexibleListProperty property) { + for(int i = 0; i < size() && i < property.size(); ++i) { + Property prop1 = get(i); + Property prop2 = property.get(i); + + if(prop1 instanceof FlexibleListProperty) ((FlexibleListProperty)prop1).unbindElements((FlexibleListProperty)prop2); + else prop1.unbindBidirectional(prop2); + } + } + + + public String getValueAsString() { + StringBuffer buffer = new StringBuffer("[ "); + + String val; + Property property = get(0); + if(property instanceof FlexibleListProperty) + val = ((FlexibleListProperty)property).getValueAsString(); + else val = property.getValue().toString(); + + if(size() == 1) buffer.append(val); + else if(size() > 1) buffer.append(val + "... "); + + buffer.append(" ]"); + return buffer.toString(); + } + + + public boolean removeProperty(Property property) { + for(Property p: get()) { + if(p instanceof FlexibleListProperty) { + if(((FlexibleListProperty) p).removeProperty(property)) { + return true; + } + } + else if(remove(property)) { + return true; + } + } + + return false; + } + + + public List<FlexibleListProperty> transpose(){ + List<FlexibleListProperty> list = new ArrayList<>(); + + for(Property property:this) { + List<Property> flat; + if(property instanceof NestedListProperty) + flat = ((NestedListProperty)property).flatten(); + else flat = ((FlexibleListProperty)property).getValue(); + + for(int i = 0; i < flat.size(); ++i) { + FlexibleListProperty listProperty;// = list.get(i); + if(list.size() <= i) { + listProperty = FlexibleListProperty.createList(getBean(), flat.get(i), protectFullName); // TODO: Check... + + list.add(listProperty); + } else { + listProperty = list.get(i); + listProperty.add(flat.get(i)); + } + } + } + + return list; + } + + + public List<Property> flatten(){ + List<Property> list = new ArrayList<>(); + + for(Property property:this) { + if(property instanceof NestedListProperty) { + list.addAll(((NestedListProperty)property).flatten()); + } + else if(property instanceof FlexibleListProperty) { + list.addAll(((FlexibleListProperty)property).get()); + } + else list.add(property); + } + + return list; + } + + + public FlexibleListProperty flattenFlex(){ + FlexibleListProperty list = createList(getBean(), flatten(), protectFullName); + return list; + } + + + private Property getCompact(List<Mark> marks) { + VisCollection tmpgroup = null, vgroup = null; + boolean iscommon = false; + + for(Mark mark:marks) { + tmpgroup = null; + if(mark instanceof VisCollection) { + tmpgroup = (VisCollection)mark; + SortedSet<PropertyName> variable = tmpgroup.getChildPropertyStructure().getVariable(); + if(variable.contains(getPropertyName())) { + vgroup = tmpgroup; + break; + } + } + } + + if(tmpgroup == null) return this; + if(vgroup == null) { + iscommon = true; + vgroup = (VisCollection)marks.get(0); + } + + if(iscommon && size() > 0) { + Property property = get(0); + if(property instanceof FlexibleListProperty) { + return ((FlexibleListProperty)property).getCompact(vgroup.getComponentAt(0)); + } else return property; + } else { + List<Property> list = new ArrayList<>(); + for(Property property: this) { + if(property instanceof FlexibleListProperty) { + list.add(((FlexibleListProperty)property).getCompact(vgroup.getComponentAt(0))); + } else list.add(property); + } + + return createList(getBean(), list, protectFullName); + } + } + + + public Property getCompact(Container refcontainer) { + if(!(refcontainer instanceof VisCollection)) return this; + + SortedSet<PropertyName> common = ((VisCollection)refcontainer).getChildPropertyStructure().getCommon(); + + if(common.contains(getPropertyName())) { + Property property = get(0); + if(property instanceof FlexibleListProperty) { + return ((FlexibleListProperty)property).getCompact(refcontainer.getComponents()); + } else return property; + } else { + List<Property> list = new ArrayList<>(); + for(Property property: this) { + if(property instanceof FlexibleListProperty) { + list.add(((FlexibleListProperty)property).getCompact(refcontainer.getComponents())); + } else list.add(property); + } + return createList(getBean(), list, protectFullName); + } + } + + + public Property getCompact2(Container refcontainer) { + if(!(refcontainer instanceof VisCollection)) return this; + + SortedSet<PropertyName> common = ((VisCollection)refcontainer).getChildPropertyStructure().getCommon(); + + List<Property> list = new ArrayList<>(); + for(Property property: this) { + if(property instanceof FlexibleListProperty) { + list.add(((FlexibleListProperty)property).getCompact(refcontainer.getComponents())); + } else list.add(property); + } + return createList(getBean(), list, protectFullName); + } + + public FlexibleListProperty getExtended() { + List<Property> list = new ArrayList<>(); + + for(Property property : flatten()) { + Mark mark = (Mark)property.getBean(); + int count = mark.leafs(); + for(int i = 0 ; i < count; ++i) + list.add(property); + } + + return createList(getBean(), list, protectFullName); + } + + + public boolean varies(Container container) { + if(!(container instanceof VisCollection)) return false; + + SortedSet<PropertyName> variable = ((VisCollection)container).getChildPropertyStructure().getVariable(); + if(variable.contains(getPropertyName())) return true; + else { + for(Mark mark:container.getComponents()) { + if(varies(mark)) return true; + } + + return false; + } + } + + + // The following methods help to deal with the binding of groups of equally valued properties for groups + public ArrayList<GroupBinding> createGroupBindings(){ + ArrayList<ArrayList<Property>> groupings = new ArrayList<>(); + for(Property property: this) { + boolean found = false; + for(ArrayList<Property> grouping: groupings) { + if(((Shareable)grouping.get(0)).isSimilar((Shareable)property) /*grouping.get(0).getValue().equals(property.getValue())*/) { + property.setValue(grouping.get(0).getValue()); + grouping.add(property); + found = true; + break; + } + } + if(!found) { + ArrayList<Property> grouping = new ArrayList<>(); + grouping.add(property); + groupings.add(grouping); + } + } + + ArrayList<GroupBinding> bindings = new ArrayList<>(); + for(ArrayList<Property> grouping:groupings) { + GroupBinding binding = new GroupBinding(grouping); + bindings.add(binding); + } + + return bindings; + } + + + @Override + public BooleanProperty getActiveProperty() { + if(isEmpty() || !(get(0) instanceof Shareable)) return new SimpleBooleanProperty(false); + else return ((Shareable)get(0)).getActiveProperty(); + } + + @Override + public boolean isSimilar(Shareable property) { + return equals(property); + } + + @Override + public BooleanProperty getPublicProperty() { + if(isEmpty() || !(get(0) instanceof Shareable)) return new SimpleBooleanProperty(false); + else return ((Shareable)get(0)).getPublicProperty(); + } + + @Override + public BooleanProperty getHiddenProperty() { + if(isEmpty() || !(get(0) instanceof Shareable)) return new SimpleBooleanProperty(false); + else return ((Shareable)get(0)).getHiddenProperty(); + } + + + public Property getByName(PropertyName pname) { + for(Property prop:this) { + String name_ = prop.getName(); + if(name_ != null && pname.toString().equals(name_)) return prop; + } + + return null; + } +} diff --git a/src/fr/inria/structgraphics/types/FontSizeProperty.java b/src/fr/inria/structgraphics/types/FontSizeProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..de7998b8473d0795db11dc6e0a14025f9d86a527 --- /dev/null +++ b/src/fr/inria/structgraphics/types/FontSizeProperty.java @@ -0,0 +1,45 @@ +package fr.inria.structgraphics.types; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class FontSizeProperty extends SimpleDoubleProperty implements Shareable { + + protected final static double EPSILON = .3; + + public FontSizeProperty(Object bean, double val) { + super(bean, "fontsize", val); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof FontSizeProperty ) { + if(Math.abs(((FontSizeProperty ) property).get() - get()) < EPSILON) return true; + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/HeightProperty.java b/src/fr/inria/structgraphics/types/HeightProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..9620bd21c4a082dfa53c06eed98b0cc842ef721e --- /dev/null +++ b/src/fr/inria/structgraphics/types/HeightProperty.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.types; + +public class HeightProperty extends DistanceProperty { + + public HeightProperty(java.lang.Object bean) { + super(bean, "height", 0); + } + + public HeightProperty(java.lang.Object bean, double value) { + super(bean, "height", value); + } + +} diff --git a/src/fr/inria/structgraphics/types/IdentifierProperty.java b/src/fr/inria/structgraphics/types/IdentifierProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..cc5db4094f81a8a9d1ea67ccd9fce9707c263aff --- /dev/null +++ b/src/fr/inria/structgraphics/types/IdentifierProperty.java @@ -0,0 +1,51 @@ +package fr.inria.structgraphics.types; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; + +public class IdentifierProperty extends SimpleStringProperty implements Shareable { + + public IdentifierProperty(Object bean, String name) { + super(bean, name); + } + + @Override + public void setValue(String value) { + if(!value.equals(get())) super.setValue(value); + else { + fireValueChangedEvent(); + } + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof IdentifierProperty) { + return getValue().equals(((IdentifierProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/LineTypeProperty.java b/src/fr/inria/structgraphics/types/LineTypeProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..2372aec7ced73fbc89a643ab36db515f0de18c72 --- /dev/null +++ b/src/fr/inria/structgraphics/types/LineTypeProperty.java @@ -0,0 +1,61 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class LineTypeProperty extends SimpleObjectProperty<LineTypeProperty.Type> implements Shareable { + + public enum Type { + None, Straight, Bezier, StraightSolid, BezierSolid, Area, TopBottom + } + + public LineTypeProperty(Object bean, Type value) { + super(bean, "curve", value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(LineTypeProperty.Type value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof LineTypeProperty) { + return getValue().equals(((LineTypeProperty) property).getValue()); + } + + return false; + } + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/NestedListProperty.java b/src/fr/inria/structgraphics/types/NestedListProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..b36b8c6d77140ee459dcc4aa1130f06e1776cc12 --- /dev/null +++ b/src/fr/inria/structgraphics/types/NestedListProperty.java @@ -0,0 +1,21 @@ +package fr.inria.structgraphics.types; + +import java.util.ArrayList; +import java.util.List; + +import javafx.beans.property.Property; +import javafx.collections.FXCollections; + +public class NestedListProperty extends FlexibleListProperty { + + public NestedListProperty(Object bean, FlexibleListProperty property) { + super(bean, property.getName(), FXCollections.observableArrayList(new ArrayList<FlexibleListProperty>())); + add(property); + } + + + public FlexibleListProperty getPropertyList(int i) { + return (FlexibleListProperty)get(i); + } + +} diff --git a/src/fr/inria/structgraphics/types/ObjectListProperty.java b/src/fr/inria/structgraphics/types/ObjectListProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..087f00af071b95e2f546c6d10d663a4a5a911915 --- /dev/null +++ b/src/fr/inria/structgraphics/types/ObjectListProperty.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.types; + +import java.util.ArrayList; +import javafx.beans.property.ObjectProperty; +import javafx.collections.FXCollections; + +public class ObjectListProperty extends FlexibleListProperty { + + public ObjectListProperty(Object bean, ObjectProperty property) { + super(bean, property.getName(), FXCollections.observableArrayList(new ArrayList<ObjectProperty>())); + add(property); + } + +} diff --git a/src/fr/inria/structgraphics/types/OpacityProperty.java b/src/fr/inria/structgraphics/types/OpacityProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..65b900f2a1ba5702847c971764793c31af2c195e --- /dev/null +++ b/src/fr/inria/structgraphics/types/OpacityProperty.java @@ -0,0 +1,60 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class OpacityProperty extends SimpleDoubleProperty implements Shareable { + + protected final static double EPSILON = .05; + + public OpacityProperty(Object bean, double val) { + super(bean, "opacity", val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Number value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof OpacityProperty) { + if(Math.abs(((OpacityProperty) property).get() - get()) < EPSILON) return true; + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/OrderProperty.java b/src/fr/inria/structgraphics/types/OrderProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..6f039709fa9643ceda1d016d71b3b539f6025b87 --- /dev/null +++ b/src/fr/inria/structgraphics/types/OrderProperty.java @@ -0,0 +1,78 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; + +public class OrderProperty extends SimpleIntegerProperty implements Shareable { + + public OrderProperty(Object bean, int val) { + super(bean, "order", val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Number value) { + if(!value.equals(get())) super.setValue(value); + else { + fireValueChangedEvent(); + } + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + + public void updateValue(double value) { + this.setValue(value); + + if(getBean() instanceof Mark) { + Container container = ((Mark)getBean()).getContainer(); + if(container instanceof VisBody) { + ((VisBody)container).updateLayout((Mark)getBean()); + } + } else if(getBean() instanceof FlowConnection) { + Container container = ((FlowConnection)getBean()).getOrigin().getContainer(); + if(container instanceof VisBody) { + ((VisBody)container).updateLayout(((FlowConnection)getBean()).getOrigin()); + } + } + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof OrderProperty) { + if(((OrderProperty) property).get() == get()) return true; + } + + return false; + } + +} diff --git a/src/fr/inria/structgraphics/types/PropertyName.java b/src/fr/inria/structgraphics/types/PropertyName.java new file mode 100644 index 0000000000000000000000000000000000000000..98a564cda0fe4e9d951113ac20eeba6a941e5b87 --- /dev/null +++ b/src/fr/inria/structgraphics/types/PropertyName.java @@ -0,0 +1,238 @@ +package fr.inria.structgraphics.types; + +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PropertyName implements Comparable<PropertyName>{ + + private static Map<String, Integer> order = createOrderMap(); + private static String prefixes = "ABCDEFGHIKLMNOP"; + + private String name, groupingName, cleanName; + private int level = 0; + private String postfix = null; + + public PropertyName(String name) { + this.name = name; + + groupingName = name.replaceAll("(\\.*[0-9]+)+", ""); + + cleanName = name.replaceAll("[A-Z].", "").replaceAll("[0-9]+", "").replaceAll("\\.", ""); + + int index = name.indexOf("."); + if(index >= 0) { + level = prefixes.indexOf(name.substring(0, index)) + 1; + } + else level = 0; + + Pattern p = Pattern.compile("[0-9]"); + Matcher m = p.matcher(name); + if (m.find()) { + postfix = name.substring(m.start()); + } + } + + public static String getRegExpr(String varname) { + return "([A-Z]\\.)?" + varname; + } + + @Override + public int compareTo(PropertyName prop) { + int res = 0; + + if(postfix != null && prop.postfix != null) res = postfix.compareTo(prop.postfix); + + if(name.endsWith("id") && !prop.name.endsWith("id")) return -1; + else if(!name.endsWith("id") && prop.name.endsWith("id")) return 1; + else if(level < prop.level) return 1; + else if(level > prop.level) return -1; + else if(res != 0) return res; + else { + return PropertyName.order.get(cleanName) - PropertyName.order.get(prop.cleanName); + } + } + + @Override + public boolean equals(Object o) { + if(o instanceof PropertyName){ + return name.equals(((PropertyName)o).name); + } + else return false; + } + + @Override + public String toString() { + return name; + } + + public String getNoPostfixName() { + return groupingName; + } + + public String getCleanName() { + return cleanName; + } + + public static String getCleanName(String name) { + return name.replaceAll("[0-9]+", "").replaceAll("\\.[0-9]+", ""); + } + + public int getLevel() { + return level; + } + + private static Map<String, Integer> createOrderMap(){ + Map<String, Integer> map = new TreeMap<>(); + map.put("id", 0); + map.put("order", 1); + map.put("source", 2); + map.put("destination", 3); + + map.put("weight", 4); + + map.put("text", 5); + map.put("fontsize", 7); + + map.put("sticky-x", 10); + map.put("sticky-y", 20); + + map.put("distribution-x", 24); + map.put("delta-x", 25); + + map.put("distribution-y", 26); + map.put("delta-y", 27); + + map.put("reference-x", 40); + map.put("reference-y", 50); + + map.put("x", 60); + map.put("y", 70); + + map.put("min-x", 73); + map.put("min-y", 74); + + map.put("width", 80); + map.put("height", 90); + map.put("h-w ratio", 92); + + map.put("shape", 94); + map.put("curve", 96); + + map.put("text", 102); + map.put("fontsize", 104); + + map.put("coloring", 110); + + map.put("fill", 112); + map.put("stroke", 114); + map.put("opacity", 116); + + + map.put("thickness", 120); + map.put("rotation", 130); + + return map; + } + + public static String getPrefix(int level) { + return prefixes.charAt(level - 1) + "."; + } + + public String getPostfix() { + return postfix; + } + + public boolean isX() { + return cleanName.equals("x"); + } + + public boolean isY() { + return cleanName.equals("y"); + } + + public boolean isID() { + return cleanName.equals("id") || cleanName.equals("source") || cleanName.equals("destination"); + } + + public boolean isWidth() { + return cleanName.equals("width"); + } + + public boolean isHeight() { + return cleanName.equals("height"); + } + + public boolean isText() { + return cleanName.equals("text"); + } + + public boolean isFontSize() { + return cleanName.equals("fontsize"); + } + + public boolean isStickyX() { + return cleanName.equals("sticky-x"); + } + + public boolean isStickyY() { + return cleanName.equals("sticky-y"); + } + + public boolean isDistributionX() { + return cleanName.equals("distribution-x"); + } + + public boolean isDistributionY() { + return cleanName.equals("distribution-y"); + } + + public boolean isDeltaX() { + return cleanName.equals("delta-x"); + } + + public boolean isDeltaY() { + return cleanName.equals("delta-y"); + } + + public boolean isReferenceX() { + return cleanName.equals("reference-x"); + } + + public boolean isReferenceY() { + return cleanName.equals("reference-y"); + } + + public boolean isShape() { + return cleanName.equals("shape"); + } + + public boolean isFill() { + return cleanName.equals("fill"); + } + + public boolean isStroke() { + return cleanName.equals("stroke"); + } + + public boolean isThickness() { + return cleanName.equals("thickness"); + } + + public boolean isRotation() { + return cleanName.equals("rotation"); + } + + public boolean isOpacity() { + return cleanName.equals("opacity"); + } + + public boolean isColoring() { + return cleanName.equals("coloring"); + } + + public boolean isCurve() { + return cleanName.equals("curve"); + } +} diff --git a/src/fr/inria/structgraphics/types/RatioLockProperty.java b/src/fr/inria/structgraphics/types/RatioLockProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..5ca6291521a0b80a8af2239c15e0c03fd1f61e25 --- /dev/null +++ b/src/fr/inria/structgraphics/types/RatioLockProperty.java @@ -0,0 +1,57 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; + +public class RatioLockProperty extends SimpleBooleanProperty implements Shareable { + + public RatioLockProperty(Object bean, String name, boolean val) { + super(bean, name, val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Boolean value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof RatioLockProperty) { + return getValue().equals(((RatioLockProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/RatioProperty.java b/src/fr/inria/structgraphics/types/RatioProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..8cc810e1b7d7f74de0eb3b32bcc36611be6b75cc --- /dev/null +++ b/src/fr/inria/structgraphics/types/RatioProperty.java @@ -0,0 +1,60 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class RatioProperty extends SimpleDoubleProperty implements Shareable { + + protected final static double EPSILON = .1; + + public RatioProperty(Object bean, String name, double val) { + super(bean, name, val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Number value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof RatioProperty) { + if(Math.abs(((RatioProperty) property).get() - get()) < EPSILON) return true; + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/RefProperty.java b/src/fr/inria/structgraphics/types/RefProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..19fa1fbe7be44c7a2e9c6f7d0bc190ca95716207 --- /dev/null +++ b/src/fr/inria/structgraphics/types/RefProperty.java @@ -0,0 +1,52 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class RefProperty<T> extends SimpleObjectProperty<T> implements Shareable { + + public RefProperty(Object bean, String name, T value) { + super(bean, name, (T)value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof RefProperty) { + return getValue().equals(((RefProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } + +} diff --git a/src/fr/inria/structgraphics/types/RotationProperty.java b/src/fr/inria/structgraphics/types/RotationProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..240c9ab736c337fde6936aed67ef7f0e157c140e --- /dev/null +++ b/src/fr/inria/structgraphics/types/RotationProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class RotationProperty extends AngleProperty { + + public RotationProperty(java.lang.Object bean) { + super(bean, "rotation", 0); + } + + public RotationProperty(java.lang.Object bean, double value) { + super(bean, "rotation", value); + } +} diff --git a/src/fr/inria/structgraphics/types/ShapeProperty.java b/src/fr/inria/structgraphics/types/ShapeProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..1c7db842dce273c1d426ec737c6eb2fbb02c02ec --- /dev/null +++ b/src/fr/inria/structgraphics/types/ShapeProperty.java @@ -0,0 +1,61 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +public class ShapeProperty extends SimpleObjectProperty<ShapeProperty.Type> implements Shareable { + + public enum Type { + Rectangle, Ellipse, Triangle, Line, Text + } + + public ShapeProperty(Object bean, Type value) { + super(bean, "shape", value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(ShapeProperty.Type value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof ShapeProperty) { + return getValue().equals(((ShapeProperty) property).getValue()); + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/Shareable.java b/src/fr/inria/structgraphics/types/Shareable.java new file mode 100644 index 0000000000000000000000000000000000000000..88211a08a758954333b5d36cc364c4c39d188735 --- /dev/null +++ b/src/fr/inria/structgraphics/types/Shareable.java @@ -0,0 +1,11 @@ +package fr.inria.structgraphics.types; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; + +public interface Shareable { + public BooleanProperty getActiveProperty(); + public boolean isSimilar(Shareable property); + public BooleanProperty getPublicProperty(); + public BooleanProperty getHiddenProperty(); +} diff --git a/src/fr/inria/structgraphics/types/StringListProperty.java b/src/fr/inria/structgraphics/types/StringListProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..2f380f2e822e8450f30ee7e7ccf6715cf7bd3c6e --- /dev/null +++ b/src/fr/inria/structgraphics/types/StringListProperty.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.types; + +import java.util.ArrayList; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; + +public class StringListProperty extends FlexibleListProperty { + + public StringListProperty(Object bean, StringProperty property) { + super(bean, property.getName(), FXCollections.observableArrayList(new ArrayList<StringProperty>())); + add(property); + } + +} diff --git a/src/fr/inria/structgraphics/types/StrokeColorProperty.java b/src/fr/inria/structgraphics/types/StrokeColorProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..efed3ac2fe458a585d3ee2198e9d1a2a2a8cb11b --- /dev/null +++ b/src/fr/inria/structgraphics/types/StrokeColorProperty.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.types; + +import javafx.scene.paint.Color; + +public class StrokeColorProperty extends ColorProperty { + + public StrokeColorProperty(java.lang.Object bean) { + super(bean, "stroke", Color.GRAY); + } + + public StrokeColorProperty(java.lang.Object bean, Color col) { + super(bean, "stroke", col); + } +} diff --git a/src/fr/inria/structgraphics/types/StrokeWidthProperty.java b/src/fr/inria/structgraphics/types/StrokeWidthProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..b9cf0765719c9e1880458838fe9bcf1fc9c1c2ff --- /dev/null +++ b/src/fr/inria/structgraphics/types/StrokeWidthProperty.java @@ -0,0 +1,63 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; + +public class StrokeWidthProperty extends SimpleDoubleProperty implements Shareable { + + protected final static double EPSILON = .1; + + public StrokeWidthProperty(Object bean) { + super(bean, "thickness", 1); + } + + public StrokeWidthProperty(Object bean, double val) { + super(bean, "thickness", val); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(Number value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof StrokeWidthProperty ) { + if(Math.abs(((StrokeWidthProperty ) property).get() - get()) < EPSILON) return true; + } + + return false; + } + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/TextProperty.java b/src/fr/inria/structgraphics/types/TextProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..3fd0908f35e6cffbb047eee9e03f6832d7fa9c55 --- /dev/null +++ b/src/fr/inria/structgraphics/types/TextProperty.java @@ -0,0 +1,57 @@ +package fr.inria.structgraphics.types; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; + +public class TextProperty extends SimpleStringProperty implements Shareable { + public TextProperty(java.lang.Object bean, String value) { + super(bean, "text", value); + } + + @Override + public String getName() { + if(getBean() instanceof Mark) { + return ((Mark)getBean()).getNestingPropertyName(super.getName()); + } else return super.getName(); + } + + @Override + public void setValue(String value) { + if(!value.equals(get())) super.setValue(value); + else fireValueChangedEvent(); + } + + private BooleanProperty activeProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getActiveProperty() { + return activeProperty; + } + + @Override + public boolean isSimilar(Shareable property) { + if(property instanceof TextProperty) { + return getValue().equals(((TextProperty) property).getValue()); + } + + return false; + } + + + + protected BooleanProperty publicProperty = new SimpleBooleanProperty(true); + + @Override + public BooleanProperty getPublicProperty() { + return publicProperty; + } + + protected BooleanProperty hiddenProperty = new SimpleBooleanProperty(false); + + @Override + public BooleanProperty getHiddenProperty() { + return hiddenProperty; + } +} diff --git a/src/fr/inria/structgraphics/types/WidthProperty.java b/src/fr/inria/structgraphics/types/WidthProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..1648aee2141570e8754a50590e51f99c4994f56d --- /dev/null +++ b/src/fr/inria/structgraphics/types/WidthProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class WidthProperty extends DistanceProperty { + + public WidthProperty(java.lang.Object bean) { + super(bean, "width", 0); + } + + public WidthProperty(java.lang.Object bean, double value) { + super(bean, "width", value); + } +} diff --git a/src/fr/inria/structgraphics/types/XMinProperty.java b/src/fr/inria/structgraphics/types/XMinProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..44ac5c56523e046145c00e492b0c3650888f5344 --- /dev/null +++ b/src/fr/inria/structgraphics/types/XMinProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class XMinProperty extends DistanceProperty { + + public XMinProperty(java.lang.Object bean) { + super(bean, "min-x", 0); + } + + public XMinProperty(java.lang.Object bean, double value) { + super(bean, "min-x", value); + } +} diff --git a/src/fr/inria/structgraphics/types/XProperty.java b/src/fr/inria/structgraphics/types/XProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..0674e2201ecb02dbd51877c9d0b62c43a9e83b20 --- /dev/null +++ b/src/fr/inria/structgraphics/types/XProperty.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.types; + +public class XProperty extends CoordinateProperty { + + public XProperty(java.lang.Object bean) { + super(bean, "x", 0); + } + + public XProperty(java.lang.Object bean, double value) { + super(bean, "x", value); + } + + +} diff --git a/src/fr/inria/structgraphics/types/YMinProperty.java b/src/fr/inria/structgraphics/types/YMinProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..842769ef0c0bcc50c199b62d1ea216c9c92dbeaa --- /dev/null +++ b/src/fr/inria/structgraphics/types/YMinProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class YMinProperty extends DistanceProperty { + + public YMinProperty(java.lang.Object bean) { + super(bean, "min-y", 0); + } + + public YMinProperty(java.lang.Object bean, double value) { + super(bean, "min-y", value); + } +} diff --git a/src/fr/inria/structgraphics/types/YProperty.java b/src/fr/inria/structgraphics/types/YProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..27f99fcd9e4b38944f6c6326da2d0b07b2fd03e7 --- /dev/null +++ b/src/fr/inria/structgraphics/types/YProperty.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.types; + +public class YProperty extends CoordinateProperty { + + public YProperty(java.lang.Object bean) { + super(bean, "y", 0); + } + + public YProperty(java.lang.Object bean, double value) { + super(bean, "y", value); + } +} diff --git a/src/fr/inria/structgraphics/ui/DisplayPreferences.java b/src/fr/inria/structgraphics/ui/DisplayPreferences.java new file mode 100644 index 0000000000000000000000000000000000000000..15238addcc092320e480a8af3bdd014dc8369c81 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/DisplayPreferences.java @@ -0,0 +1,7 @@ +package fr.inria.structgraphics.ui; + +public class DisplayPreferences { + + public final static double CANVAS_WIDTH = 1600, CANVAS_HEIGHT = 1400; + +} diff --git a/src/fr/inria/structgraphics/ui/DragManager.java b/src/fr/inria/structgraphics/ui/DragManager.java new file mode 100644 index 0000000000000000000000000000000000000000..69f6b8264b0e98335ee995cb38ea5c0a654683c4 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/DragManager.java @@ -0,0 +1,51 @@ +package fr.inria.structgraphics.ui; + +import fr.inria.structgraphics.ui.inspector.InspectorView; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.TransferMode; + +public class DragManager { + public final static String DRAG_CODE = "InFoGraPHeR"; + + private static DragManager instance; + + private InspectorView inspector; + + public DragManager(InspectorView inspector) { + this.inspector = inspector; + } + + public static DragManager create(InspectorView inspector) { + instance = new DragManager(inspector); + + return instance; + } + + public static DragManager getSingleton() { + return instance; + } + + public void observe(Draggable draggable) { + Node node = draggable.getNode(); + + node.setOnDragDetected( + new EventHandler<MouseEvent>() { public void handle(MouseEvent event) { + /* drag was detected, start a drag-and-drop gesture*/ + /* allow any transfer mode */ + Dragboard db = node.startDragAndDrop(TransferMode.COPY); +// System.err.println(draggable.getID()); + /* Put a string on a dragboard */ + ClipboardContent content = new ClipboardContent(); + content.putString(DRAG_CODE); + db.setContent(content); + inspector.setDraggable(draggable); + + event.consume(); + } + }); + } +} diff --git a/src/fr/inria/structgraphics/ui/Draggable.java b/src/fr/inria/structgraphics/ui/Draggable.java new file mode 100644 index 0000000000000000000000000000000000000000..ff11320e4da9b844d7e5254d355a1f739e1ddad6 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/Draggable.java @@ -0,0 +1,26 @@ +package fr.inria.structgraphics.ui; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.PropertyName; +import javafx.scene.Node; + +public interface Draggable { + + public enum Type { + Value, Table, Column + } + + public VisBody getGroup(); + public VisBody getTopGroup(); + public Container getContainer(); + public Node getNode(); + public Type getType(); + public Object getPropertiesContent(); + public PropertyName getID(); + public int getColumnsNum(); + public int getRowsNum(); + public void update(); + public boolean isInWide(); + public boolean isNetwork(); +} diff --git a/src/fr/inria/structgraphics/ui/inspector/BasePropertiesPane.java b/src/fr/inria/structgraphics/ui/inspector/BasePropertiesPane.java new file mode 100644 index 0000000000000000000000000000000000000000..6d1e4a66f5b383c2ddcd718b5964673dfa0eeefe --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/BasePropertiesPane.java @@ -0,0 +1,82 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.Collection; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.ui.inspector.PropertiesTab.Type; +import javafx.beans.property.Property; +import javafx.geometry.Pos; +import javafx.scene.Node; + + +public class BasePropertiesPane extends PropertiesPane { + + protected IndividualPropertiesGrid basegrid; + protected TitledIndividualPropertiesPane sharedpane; + + protected TitledTablePane tabularpane; + protected TabularPropertiesGrid fulltabularpane; + + static PropertyDetail activeDetail; + + protected Container node; + + public BasePropertiesPane(InspectorView inspector, String name, Container node) { + this(inspector, name, node, Type.SELF); + + } + + public BasePropertiesPane(InspectorView inspector, String name, Container node, Type type) { + super(inspector, name); + this.node = node; + + getStyleClass().add("detail-pane"); + setManaged(false); + setVisible(false); + setExpanded(false); + setMaxWidth(Double.MAX_VALUE); + setId("title-label"); + setAlignment(Pos.CENTER_LEFT); + + if(type == Type.SELF || type == Type.GROUP || type == Type.PUBLIC) { + basegrid = new IndividualPropertiesGrid(node, type); + addPane(basegrid); + } else if(type == Type.CHILDREN) { + sharedpane = new TitledIndividualPropertiesPane("shared", new IndividualPropertiesGrid(node, type)); + addPane(sharedpane); + } + } + + protected void addToPane(final Node... nodes) { + if(basegrid != null) basegrid.addToPane(nodes); + else if(sharedpane != null) sharedpane.addToPane(nodes); + } + + @Override + protected void clearPane() { + if(basegrid != null) basegrid.clearPane(); + if(sharedpane != null) sharedpane.clearPane(); + if(tabularpane != null) tabularpane.clearPane(); + if(fulltabularpane != null) fulltabularpane.clearPane(); + } + + @Override + public void addDetails(final Collection<Property> properties) { + if(basegrid != null) basegrid.addDetails(properties); + else if(sharedpane != null) sharedpane.addDetails(properties); + } + + + @Override + public void refresh() { + if(basegrid != null) basegrid.refresh(); + if(sharedpane != null) sharedpane.refresh(); + if(tabularpane != null) tabularpane.refresh(); + if(fulltabularpane != null) fulltabularpane.refresh(); + } + + public void update() { + refresh(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/CollectionPropertiesPane.java b/src/fr/inria/structgraphics/ui/inspector/CollectionPropertiesPane.java new file mode 100644 index 0000000000000000000000000000000000000000..f8eb83bb83efb28100e9e4274ced7749a2e2b240 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/CollectionPropertiesPane.java @@ -0,0 +1,172 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.Collection; +import java.util.SortedSet; + +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.DragManager; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.inspector.PropertiesTab.Type; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.event.EventHandler; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; + +public class CollectionPropertiesPane extends BasePropertiesPane { + + private CollectionPropertyStructure structure; + + public CollectionPropertiesPane(InspectorView inspector, String name, VisCollection node, Type type) { + super(inspector, name, node, type); + this.structure = node.getChildPropertyStructure(); + + structure.getFlagProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + inspector.getPropertiesTable().updateStructures(); + } + }); + } + + // This adds the extended (detailed) tabular form + public void addExtendedTable(String name) { // TODO: This needs work !!! + PropertiesTable table = new PropertiesTable(name, structure, true, node); + fulltabularpane = new TabularPropertiesGrid(false); + fulltabularpane.addPropertiesTable(table); + + addPane(fulltabularpane); + } + + public void addTabDetails(String name) { + addDetails(structure.getMerged(), structure.getCommon()); + + PropertiesTable table = new PropertiesTable(name, structure, node); + //tabularpane = new TabularPropertiesPane(name); + tabularpane = new TitledTablePane(name, new TabularPropertiesGrid(true)); + tabularpane.addPropertiesTable(table); + addPane(tabularpane); + + sharedpane.setOnDragOver(new EventHandler<DragEvent>() { + + public void handle(DragEvent event) { + + Dragboard db = event.getDragboard(); + + //TODO: I need to fix that so that it only activate when + if (inspector.getDragged().getPropertiesContent() instanceof FlexibleListProperty && db.hasString() && !(inspector.getDragged() instanceof PropertyDetail)) { + /* allow for both copying and moving, whatever user chooses */ + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + + // spreadsheetView.addEventHandler(MouseEvent.MOUSE_MOVED, eventHandler); + } + event.consume(); + } + }); + + tabularpane.setOnDragOver(new EventHandler<DragEvent>() { + public void handle(DragEvent event) { + Dragboard db = event.getDragboard(); + + //TODO: I need to fix that so that it only activate when + Draggable dragged = inspector.getDragged(); + if(dragged == null) { + event.consume(); + return; + } + + Object content = dragged.getPropertiesContent(); + if ((content instanceof ObjectProperty || content instanceof DoubleProperty || content instanceof FlexibleListProperty) && (inspector.getDragged() instanceof PropertyDetail) && db.hasString() && inspector.getDragged().getID() != null) { + /* allow for both copying and moving, whatever user chooses */ + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + + // spreadsheetView.addEventHandler(MouseEvent.MOUSE_MOVED, eventHandler); + } + event.consume(); + } + }); + + + sharedpane.setOnDragDropped(new EventHandler <DragEvent>() { + public void handle(DragEvent event) { + /* data dropped */ + /* if there is a string data on dragboard, read it and use it */ + Dragboard db = event.getDragboard(); + boolean success = false; + if (event.getGestureSource() != sharedpane && db.hasString() && db.hasString() && db.getString().equals(DragManager.DRAG_CODE)) { + dragEnded(inspector.getDragged()); + success = true; + } + /* let the source know whether the string was successfully + * transferred and used */ + event.setDropCompleted(success); + + event.consume(); + } + }); + + + tabularpane.setOnDragDropped(new EventHandler <DragEvent>() { + public void handle(DragEvent event) { + /* data dropped */ + /* if there is a string data on dragboard, read it and use it */ + Dragboard db = event.getDragboard(); + boolean success = false; + if (db.hasString() && db.getString().equals(DragManager.DRAG_CODE)) { + dragEnded(inspector.getDragged()); + success = true; + } + /* let the source know whether the string was successfully + * transferred and used */ + event.setDropCompleted(success); + + event.consume(); + } + }); + } + + + private void addDetails(Collection<Property> properties, SortedSet<PropertyName> common) { + sharedpane.addDetails(properties, common); + } + + + @Override + public void update() { + if(sharedpane != null) sharedpane.clearPane(); + + if(tabularpane !=null) { + addDetails(structure.getMerged(), structure.getCommon()); + tabularpane.getTable().refresh(); + tabularpane.refreshGrid(); + } else if(fulltabularpane !=null) { + fulltabularpane.getTable().refresh(); + fulltabularpane.refreshGrid(); + } + + refresh(); + } + + + protected void dragEnded(Draggable dragged) { + //Object content = dragged.getPropertiesContent(); + if(dragged instanceof PropertyDetail) { // Need to move to the simple properties space + structure.moveToVariable(dragged.getID()); + } else { // TODO: add it to the table + structure.moveToCommon(dragged.getID()); + //inspector.getVisualizationFrame().update(); + } + + // TODO... + // inspector.getPropertiesTable().updateStructures(); + } + +} + diff --git a/src/fr/inria/structgraphics/ui/inspector/ColorStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/ColorStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..b69d612c275b9f14e69c2fc7982ff5c5a2c78643 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/ColorStringConverter.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.ui.inspector; + +import javafx.scene.paint.Color; + +public class ColorStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + return Color.web(string); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/ColoringStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/ColoringStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..72399db2748db2c75bbe09423cfd00f07dcafc0c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/ColoringStringConverter.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.types.ColoringSchemeProperty; + +public class ColoringStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("common")) return ColoringSchemeProperty.Scheme.Common; + else if(string.equalsIgnoreCase("source")) return ColoringSchemeProperty.Scheme.Source; + else return ColoringSchemeProperty.Scheme.Destination; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/DisplayUtils.java b/src/fr/inria/structgraphics/ui/inspector/DisplayUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..561d862c2253644bd6fa035c8f6475d964b5d5e7 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/DisplayUtils.java @@ -0,0 +1,101 @@ +/* + * Scenic View, + * Copyright (C) 2012 Jonathan Giles, Ander Ruiz, Amy Fowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package fr.inria.structgraphics.ui.inspector; + +import java.net.URL; +import java.text.DecimalFormat; +import java.util.*; +import java.util.logging.*; + +import javafx.scene.image.Image; + +/** + * + */ +public class DisplayUtils { + + // From: https://www.flaticon.com/free-icon/cleaning_1215980 + + private static final String CUSTOM_NODE_IMAGE = DisplayUtils.getNodeIcon("CustomNode").toString(); + private static final Map<String, Image> loadedImages = new HashMap<>(); + + public static DecimalFormat DFMT = new DecimalFormat("0.0#"); + private static Level wLevel; + private static Level wpLevel; + + private static URL getNodeIcon(final String node) { + return InspectorView.class.getResource("icons/" + node + ".png"); + } + + public static Image getIcon(String type) { + Image image = loadedImages.get(type); + if (image == null) { + final URL resource = DisplayUtils.getNodeIcon(type); + String url; + if (resource != null) { + url = resource.toString(); + } else { + url = CUSTOM_NODE_IMAGE; + } + image = new Image(url); + loadedImages.put(type, image); + } + return image; + } + + + public static Image getIcon(final VisualizationNode visNode) { + if (visNode.getIcon() != null) + return visNode.getIcon(); + Image image = loadedImages.get(visNode.getType()); + if (image == null) { + final URL resource = DisplayUtils.getNodeIcon(visNode.getType()); + String url; + if (resource != null) { + url = resource.toString(); + } else { + url = CUSTOM_NODE_IMAGE; + } + image = new Image(url); + loadedImages.put(visNode.getType(), image); + } + return image; + } + + public static void showWebView(final boolean show) { + if (show) { + /** + * Ugly patch to remove the visual trace of the WebPane + */ + final Logger webLogger = java.util.logging.Logger.getLogger("com.sun.webpane"); + final Logger webPltLogger = java.util.logging.Logger.getLogger("webcore.platform.api.SharedBufferInputStream"); + wLevel = webLogger.getLevel(); + wpLevel = webPltLogger.getLevel(); + webLogger.setLevel(Level.SEVERE); + webPltLogger.setLevel(Level.SEVERE); + } else { + Logger.getLogger("com.sun.webpane").setLevel(wLevel); + Logger.getLogger("webcore.platform.api.SharedBufferInputStream").setLevel(wpLevel); + } + } + + public static Image getUIImage(final String image) { + return new Image(InspectorView.class.getResource("icons/" + image).toString()); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/DistributionStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/DistributionStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..509fc18aaf88b992764bf1f93726d3e3fa25fed8 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/DistributionStringConverter.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.types.DistributionProperty.Constraint; + +public class DistributionStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("none")) return Constraint.None; + else if(string.equalsIgnoreCase("spacing")) return Constraint.Spacing; + else return Constraint.Distance; + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/DoubleStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/DoubleStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..38bc7f6cce72293e12b1d5ba0a41f6defcf4d95b --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/DoubleStringConverter.java @@ -0,0 +1,27 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.text.DecimalFormat; + +public class DoubleStringConverter extends GeneralStringConverter { + + private boolean decimals = false; + + public DoubleStringConverter() { + this(false); + } + + public DoubleStringConverter(boolean decimals) { + this.decimals = decimals; + } + + @Override + public Object fromString(String string) { + return Double.parseDouble(string); + } + + @Override + public String toString(Object object) { + DecimalFormat df = new DecimalFormat(decimals ? "#.0" : "#"); + return df.format((Double)object); + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/FlowConnectionsPane.java b/src/fr/inria/structgraphics/ui/inspector/FlowConnectionsPane.java new file mode 100644 index 0000000000000000000000000000000000000000..a367bab341be1d9cbba3a51630284167721da4be --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/FlowConnectionsPane.java @@ -0,0 +1,54 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.ui.inspector.PropertiesTab.Type; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnections; + +public class FlowConnectionsPane extends BasePropertiesPane { + + private FlowConnections flowConnections; + + public FlowConnectionsPane(InspectorView inspector, String name, LineConnectedCollection node, Type type) { + super(inspector, name, node, type); + this.flowConnections = node.getFlowConnections(); + + sharedpane = new TitledIndividualPropertiesPane("visuals", new IndividualPropertiesGrid(node, type)); + addPane(sharedpane); + + // TODO: Add something like that to observe changes!!!! + /* + flowConnections.getFlagProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + //inspector.getPropertiesTable().updateStructures(); + } + });*/ + } + + // This adds the extended (detailed) tabular form + public void addExtendedTable(String name) { // TODO: This needs work !!! + + //tabularpane = new TabularPropertiesPane(name); + tabularpane = new TitledTablePane(name, new TabularPropertiesGrid(true)); + FlowConnectionsTable table = new FlowConnectionsTable(name, flowConnections, node); + tabularpane = new TitledTablePane(name, new TabularPropertiesGrid(true)); + tabularpane.addPropertiesTable(table); + + //fulltabularpane = new TabularPropertiesGrid(false); + //fulltabularpane.addPropertiesTable(table); + + addPane(tabularpane); + } + + @Override + public void update() { + if(fulltabularpane !=null) { + fulltabularpane.getTable().refresh(); + fulltabularpane.refreshGrid(); + } + + refresh(); + } + +} + diff --git a/src/fr/inria/structgraphics/ui/inspector/FlowConnectionsTable.java b/src/fr/inria/structgraphics/ui/inspector/FlowConnectionsTable.java new file mode 100644 index 0000000000000000000000000000000000000000..b6e75252da6c8df9a8e52d96c52bc188d7eca1d0 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/FlowConnectionsTable.java @@ -0,0 +1,50 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.Map; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnections; + +public class FlowConnectionsTable extends PropertiesTable { + + private FlowConnections flowConnections = null; + + public FlowConnectionsTable(String name, FlowConnections flowConnections, Container node) { + super(name, node); + + this.full = false; + this.flowConnections = flowConnections; + + props = flowConnections.getVariableList(); + addEntries(props); + } + + @Override + protected void addEntries(Map<PropertyName, FlexibleListProperty> tabproperties) { + for(PropertyName name: tabproperties.keySet()) { + FlexibleListProperty properties = tabproperties.get(name); + + PropertyDetailColumn column = new FlowPropertyDetailColumn(this, properties, name, node); + column.add(properties); + add(column); + } + } + + @Override + public boolean isNetwork() { + return true; + } + + @Override + public int getColumnsNum() { // TO Change + return 3; + } + + @Override + public int getRowsNum() { + if(flowConnections.isEmpty()) return 0; + else return flowConnections.size() + 1; + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/FlowPropertyDetailColumn.java b/src/fr/inria/structgraphics/ui/inspector/FlowPropertyDetailColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..bca5752bb90493c61be4f37678be9f99a8462922 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/FlowPropertyDetailColumn.java @@ -0,0 +1,22 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; + +public class FlowPropertyDetailColumn extends PropertyDetailColumn { + + private boolean isID = false; + + public FlowPropertyDetailColumn(FlowConnectionsTable table, FlexibleListProperty properties, PropertyName col, Container node) { + super(table, properties, col, node); + + isID = col.isID(); + } + + @Override + public int getColumnsNum() { + return isID ? 2 : 1; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/GeneralStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/GeneralStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..192ba658bae96a6756d034f297f4e1cd7903a69d --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/GeneralStringConverter.java @@ -0,0 +1,17 @@ +package fr.inria.structgraphics.ui.inspector; + +import javafx.util.StringConverter; + +public class GeneralStringConverter extends StringConverter { + + @Override + public String toString(Object object) { + return object.toString(); + } + + @Override + public Object fromString(String string) { + return string; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/GroupPropertiesPane.java b/src/fr/inria/structgraphics/ui/inspector/GroupPropertiesPane.java new file mode 100644 index 0000000000000000000000000000000000000000..03d04ef6d1e68b3767c9b840c401aa8692c81d1f --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/GroupPropertiesPane.java @@ -0,0 +1,55 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.ui.inspector.PropertiesTab.Type; +import fr.inria.structgraphics.ui.viscanvas.groupings.GroupPropertyStructure; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; + +public class GroupPropertiesPane extends BasePropertiesPane { + + private GroupPropertyStructure structure; + + public GroupPropertiesPane(InspectorView inspector, String name, VisGroup node, Type type) { + super(inspector, name, node, type); + this.structure = node.getChildPropertyStructure(); + + structure.getFlagProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + inspector.getPropertiesTable().updateStructures(); + } + }); + } + + public void addTabDetails() { + basegrid.addDetails(structure); + //basegrid.addDetails(structure.getFlattenedProperties(), structure.getNames(), structure.getCommon()); + } + + @Override + public void update() { + if(basegrid != null) { + basegrid.clearPane(); + basegrid.addDetails(structure); + } + refresh(); + } + + + /* + protected void dragEnded(Draggable dragged) { + //Object content = dragged.getPropertiesContent(); + if(dragged instanceof PropertyDetail) { // Need to move to the simple properties space + structure.moveToVariable(dragged.getID()); + } else { // TODO: add it to the table + structure.moveToCommon(dragged.getID()); + //inspector.getVisualizationFrame().update(); + } + + // TODO... + // inspector.getPropertiesTable().updateStructures(); + }*/ + +} + diff --git a/src/fr/inria/structgraphics/ui/inspector/IndividualPropertiesGrid.java b/src/fr/inria/structgraphics/ui/inspector/IndividualPropertiesGrid.java new file mode 100644 index 0000000000000000000000000000000000000000..3fdfdf6253db932e95794811cdda9fb4444d3105 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/IndividualPropertiesGrid.java @@ -0,0 +1,254 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.HeightProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.types.WidthProperty; +import fr.inria.structgraphics.ui.DragManager; +import fr.inria.structgraphics.ui.inspector.PropertiesTab.Type; +import fr.inria.structgraphics.ui.viscanvas.groupings.DefaultSharingStrategy; +import fr.inria.structgraphics.ui.viscanvas.groupings.GroupPropertyStructure; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; + + +public class IndividualPropertiesGrid extends GridPane { + + private static final int LABEL_COLUMN = 0; + private static final int VALUE_COLUMN = 1; + private static final int LOCK_COLUMN = 2; + + static final String DETAIL_LABEL_STYLE = "detail-label"; + + private List<PropertyDetail> details = new ArrayList<>(); + + private Container node; + + private Type type; + + public IndividualPropertiesGrid(Container node, Type type) { + super(); + + this.node = node; + this.type = type; + + getStyleClass().add("detail-pane"); + + getStyleClass().add("detail-grid"); + setOnMousePressed(event -> { + if(BasePropertiesPane.activeDetail != null) BasePropertiesPane.activeDetail.recover(); + }); + + setHgap(4); + setVgap(2); + setSnapToPixel(true); + // final ColumnConstraints colInfo = new ColumnConstraints(120); + + final ColumnConstraints colInfo = new ColumnConstraints(10); + getColumnConstraints().addAll(colInfo, new ColumnConstraints()); + } + + private void updateColumnWidth() { + double w = 20; + + for(PropertyDetail detail: details) { + Text theText = new Text(detail.label.getText()); + theText.setFont(detail.label.getFont()); + double width = theText.getBoundsInLocal().getWidth(); + w = Math.max(w, 20 + width); + } + + getColumnConstraints().remove(0); + getColumnConstraints().add(0, new ColumnConstraints(w)); + } + + + private ChangeListener<Boolean> widthListener = null, heightListener = null; + + protected void addDetail(Property property, int row, PropertyName id, GroupPropertyStructure structure) { + + PropertyDetail detail; + detail = new PropertyDetail(property, false, type == Type.PUBLIC, id, node); + + // TODO: The source of the problem is here!!! + DragManager.getSingleton().observe(detail); + + final HBox labelgroup = new HBox(8, detail.label); + labelgroup.setAlignment(Pos.TOP_RIGHT); + GridPane.setConstraints(labelgroup, LABEL_COLUMN, row); + GridPane.setHalignment(labelgroup, HPos.RIGHT); + GridPane.setValignment(labelgroup, VPos.TOP); + detail.label.getStyleClass().add(DETAIL_LABEL_STYLE); + + final HBox valuegroup = new HBox(detail.valueLabel); + GridPane.setConstraints(valuegroup, VALUE_COLUMN, row); + GridPane.setHalignment(valuegroup, HPos.LEFT); + GridPane.setValignment(valuegroup, VPos.TOP); + detail.valueLabel.getStyleClass().add("detail-value"); + + // TODO: + if(type != Type.PUBLIC && (property instanceof HeightProperty || property instanceof WidthProperty) && id == null && (property.getBean() instanceof Mark)) { // This only concerns the Self Properties pane + Mark mark = (Mark)property.getBean(); + + detail.createLockIcon(mark.ratiolock.get(), property instanceof WidthProperty); + + labelgroup.getChildren().add(0, detail.lockIcon); + + detail.lockIcon.setOnMouseClicked(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + mark.ratiolock.set(!mark.ratiolock.get()); + //detail.switchLock(mark.ratiolock.get()); + } + }); + + + if(property instanceof HeightProperty) { + if(heightListener != null) mark.ratiolock.removeListener(heightListener); + heightListener = new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + detail.switchLock(newValue); + } + }; + mark.ratiolock.addListener(heightListener); + } else if (property instanceof WidthProperty) { + if(widthListener != null) mark.ratiolock.removeListener(widthListener); + widthListener = new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + detail.switchLock(newValue); + } + }; + mark.ratiolock.addListener(widthListener); + } + } + else if(structure != null) { + boolean flag = structure.getCommon().contains(id); + detail.createExpandIcon(flag); + labelgroup.getChildren().add(0, detail.lockIcon); + detail.lockIcon.setOnMouseClicked(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if(flag) structure.moveToVariable(id); + else structure.moveToCommon(id); + detail.switchLock(!flag); + + // Dirty solution to update the public properties + FlexibleListProperty property = structure.getProperties().get(id); + BooleanProperty prop = ((Shareable)property.get(0)).getPublicProperty(); + prop.set(!prop.get()); + prop.set(!prop.get()); + + // Propagate change to groups under the same collection + // TODO: There is a problem when the properties of the siblings do not allow + // for a sharing ... Do I need a way to force the sharing? + Object owner = property.getBean(); + if(owner instanceof Mark) { + Container group = ((Mark)owner).getContainer(); + if(group instanceof VisCollection) { + for(Mark mark: group.getComponents()) { + if(mark != owner && mark instanceof VisGroup) { + GroupPropertyStructure struct = ((VisGroup)mark).getChildPropertyStructure(); + if(flag) struct.moveToVariable(id); + else struct.moveToCommon(id); + FlexibleListProperty property2 = structure.getProperties().get(id); + BooleanProperty prop2 = ((Shareable)property2.get(0)).getPublicProperty(); + prop2.set(!prop2.get()); + prop2.set(!prop2.get()); + } + } + + // TODO: May contain bugs...Do other collections in the hierarchy need updating? + ((VisCollection)group).getChildPropertyStructure().refresh(new DefaultSharingStrategy()); + } + } + } + }); + } + + addToPane(labelgroup, valuegroup); + + details.add(detail); + updateColumnWidth(); + } + + + protected void addToPane(final Node... nodes) { + getChildren().addAll(nodes); + } + + protected void clearPane() { + getChildren().clear(); + details.clear(); + // TODO.... + } + + public void addDetails(final Collection<Property> properties) { + for(Property property: properties) { + addDetail(property, null); + } + } + + public void addDetails(final Collection<Property> properties, Set<PropertyName> common) { + Iterator<PropertyName> iterator = common.iterator(); + + for (Property property: properties) { // Hide non-shareable group properties + if(property instanceof Shareable && (!((Shareable) property).getPublicProperty().get() + || ((Shareable) property).getHiddenProperty().get())) { + iterator.next(); + continue; + } + else addDetail(property, iterator.next()); + } + } + + public void addDetails(GroupPropertyStructure structure) { + // TODO Auto-generated method stub + + Iterator<PropertyName> iterator = structure.getNames().iterator(); + for (Property property: structure.getFlattenedProperties()) { + PropertyName name = iterator.next(); + addDetail(property, details.size(), name, structure); + } + } + + public void addDetail(Property detail, PropertyName id) { //System.err.println(id + " " + detail); + addDetail(detail, details.size(), id, null); + } + + public void refresh() { + for (int i = 0; i < details.size(); i++) { + details.get(i).refresh(); + } + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/InspectorView.java b/src/fr/inria/structgraphics/ui/inspector/InspectorView.java new file mode 100644 index 0000000000000000000000000000000000000000..deb1f36cc807bf6e8b40f3117544cb4d28b106a0 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/InspectorView.java @@ -0,0 +1,143 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.spreadsheet.DataView; +import fr.inria.structgraphics.ui.tools.SelectTool; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TabPane; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; + +public class InspectorView extends BorderPane { + + public static final String STYLESHEETS = InspectorView.class.getResource("inspector.css").toExternalForm(); + + private SplitPane splitPane; + private VisualizationTreeView treeView; + private TabPane tabPane; + private PropertiesTab propertiesTab; + private SyntaxTab syntaxTab; + + private VisFrame visframe; + + private Draggable dragged = null; + + private DataView dataView; + + public InspectorView() { + buildUI(); + } + + private void buildUI() { + // main splitpane + splitPane = new SplitPane(); + splitPane.setId("splitpane"); + + treeView = new VisualizationTreeView(this); + treeView.setId("treeview"); + + StackPane treeViewStackPane = new StackPane(treeView); + treeViewStackPane.setStyle(" -fx-padding: 0"); + treeView.setMaxHeight(Double.MAX_VALUE); + + tabPane = new TabPane(); + propertiesTab = new PropertiesTab(this); + tabPane.getTabs().add(propertiesTab); + + + // TODO: I do not care about syntax for now... + // syntaxTab = new SyntaxTab(this); + // tabPane.getTabs().add(syntaxTab); + + splitPane.setDividerPosition(0, 0.3); + splitPane.getItems().addAll(treeViewStackPane, tabPane); + + this.setCenter(splitPane); + } + + public void setVisualizationFrame(VisFrame visframe) { + this.visframe = visframe; + visframe.setInspector(this); + + // TODO: No handling syntax for now + // syntaxTab.setSyntax(visframe.getSyntax()); + } + + public void update() { + propertiesTab.cleanAll(); + treeView.update(visframe); + } + + public void updateOrder() { + propertiesTab.cleanAll(); + treeView.updateOrder(); + dataView.update(); + } + + public VisFrame getVisualizationFrame() { + return visframe; + } + + + public void refresh() { + // propertiesTab.cleanAll(); + // treeView.refreshProperties(visframe); + } + + public PropertiesTab getPropertiesTable() { + return propertiesTab; + } + + public void setView(ArrayList<Mark> marks) { + treeView.setActiveNodes(marks); + } + + public void setView(Mark mark) { + treeView.setActiveNode(mark); + } + + public void updateTreeViewLabel(Mark mark) { + treeView.updateLabel(mark); + } + + public void selectView(Container node) { + // treeView.selectView(node); + // propertiesTab.setActiveNode(node); + } + + public void generateView(Container node) { + propertiesTab.publishNode(node); + } + + public void updateCollectionViews(VisCollection collection) { + propertiesTab.publishNode(collection); + for(Mark mark:collection.getComponents()) { + if(mark instanceof VisCollection) + updateCollectionViews((VisCollection)mark); + } + } + + public void setDraggable(Draggable draggable) { + dragged = draggable; + } + + public Draggable getDragged() { + return dragged; + } + + public void setSelector(SelectTool select) { + treeView.setSelector(select); + } + + public void setDataView(DataView dataView) { + this.dataView = dataView; + + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/LineTypeStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/LineTypeStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..f3f90417f19f4038a28d4e388046ee7371c4e102 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/LineTypeStringConverter.java @@ -0,0 +1,17 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.types.LineTypeProperty.Type; + +public class LineTypeStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("None")) return Type.None; + else if(string.equalsIgnoreCase("StraightSolid")) return Type.StraightSolid; + else if(string.equalsIgnoreCase("Straight")) return Type.Straight; + else if(string.equalsIgnoreCase("Bezier")) return Type.Bezier; + else if(string.equalsIgnoreCase("BezierSolid")) return Type.BezierSolid; + else if(string.equalsIgnoreCase("TopBottom")) return Type.TopBottom; + else /*if(string.equalsIgnoreCase("Area")*/ return Type.Area; + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/ListStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/ListStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..c3f6de7167c2919f8d22e72f4ee4c7758a133476 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/ListStringConverter.java @@ -0,0 +1,28 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; +import java.util.List; + +import javafx.beans.property.Property; +import javafx.util.StringConverter; + +public class ListStringConverter extends StringConverter<List<Property>> { + + @Override + public String toString(List<Property> list) { + StringBuffer buffer = new StringBuffer("[ "); + + if(list.size() == 1) buffer.append(list.get(0).getValue()); + else if(list.size() > 1) buffer.append(list.get(0).getValue() + "... "); + + buffer.append(" ]"); + + return buffer.toString(); + } + + @Override + public List<Property> fromString(String string) { + return new ArrayList<>(); // TODO: This is not useful. Not sure if it will be eventually used... + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/PropertiesPane.java b/src/fr/inria/structgraphics/ui/inspector/PropertiesPane.java new file mode 100644 index 0000000000000000000000000000000000000000..0127f61b15bdaad08b0f072371b393cc9d260e2e --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/PropertiesPane.java @@ -0,0 +1,40 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.Collection; + +import javafx.beans.property.Property; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.VBox; + +public abstract class PropertiesPane extends TitledPane { + + protected InspectorView inspector; + private VBox box; + + public PropertiesPane(InspectorView inspector, String name) { + super(); + + this.inspector = inspector; + setText(name); + + box = new VBox(); + setContent(box); + } + + public void addPane(Node node) { + box.getChildren().add(node); + } + + public void addGap() { + box.getChildren().add(new Label(" ")); + } + + public abstract void addDetails(final Collection<Property> details); + + protected abstract void clearPane(); + public abstract void refresh(); + public abstract void update(); + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/PropertiesTab.java b/src/fr/inria/structgraphics/ui/inspector/PropertiesTab.java new file mode 100644 index 0000000000000000000000000000000000000000..de03d8b6de6c6c1db31df9cb33ebf8acdf17d56f --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/PropertiesTab.java @@ -0,0 +1,288 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnections; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tab; +import javafx.scene.layout.VBox; + +public class PropertiesTab extends Tab { + + public enum Type { + SELF, CHILDREN, TABLE, GROUP, PUBLIC, CONNECTION_TABLE + } + + public static final String TAB_NAME = "Properties Structure"; + private InspectorView inspector; + + private Container activeNode = null; + + private Hashtable<NodeEntry, PropertiesPane> propertiesPanes = new Hashtable<NodeEntry, PropertiesPane>(); + + private VBox vbox; + + public PropertiesTab(InspectorView inspector) { + super(TAB_NAME); + + this.inspector = inspector; + + ScrollPane scrollPane = new ScrollPane(); + //scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setFitToWidth(true); + vbox = new VBox(); + vbox.setFillWidth(true); + scrollPane.setContent(vbox); + getStyleClass().add("all-details-pane"); + + setContent(scrollPane); + setClosable(false); + } + + + public void setActiveNode(Container node) { + if(activeNode == node) return; + else clean(); + + PropertiesPane pane = getPane(node, Type.SELF); + pane.setExpanded(true); + pane.setManaged(true); + pane.setVisible(true); + + ////////// + if(node instanceof VisCollection) { // TODO: Deal with Groups!!!!! + pane = getPane(node, Type.CHILDREN); + pane.setExpanded(true); + pane.setManaged(true); + pane.setVisible(true); + + pane = getPane(node, Type.TABLE); + pane.setExpanded(true); + pane.setManaged(true); + pane.setVisible(true); + + // For Top collections show the connections if available + if(node instanceof LineConnectedCollection) { + FlowConnections connections = ((LineConnectedCollection) node).getFlowConnections(); + if(connections != null && !connections.isEmpty()) { + pane = getPane(node, Type.CONNECTION_TABLE); + pane.setExpanded(true); + pane.setManaged(true); + pane.setVisible(true); + } + } + } + else if(node instanceof VisGroup && !(((VisGroup) node).getContainer() instanceof VisGroup)) { + pane = getPane(node, Type.GROUP); + pane.setExpanded(true); + pane.setManaged(true); + pane.setVisible(true); + + pane = getPane(node, Type.PUBLIC); + pane.setExpanded(true); + pane.setManaged(true); + pane.setVisible(true); + } + } + + + public void publishNode(Container node) { + if(node instanceof VisGroup && !(((VisGroup) node).getContainer() instanceof VisGroup)) { + createPane(node, Type.PUBLIC); + } + + createPane(node, Type.SELF); + + if(node instanceof VisCollection) { + createPane(node, Type.CHILDREN); + createPane(node, Type.TABLE); + + if(node.hasConnections()) createPane(node, Type.CONNECTION_TABLE); + } + else if (node instanceof VisGroup && !(((VisGroup) node).getContainer() instanceof VisGroup)) { + createPane(node, Type.GROUP); + } + } + + + private void createPane(Container node, Type type) { + String paneName = ""; + + if(type == Type.SELF) + paneName = "Self Properties"; + else if(type == Type.CHILDREN) + paneName = "Children Properties"; + else if(type == Type.GROUP) + paneName = "Children Properties"; + else if(type == Type.TABLE) + paneName = "Tabular Structure"; + else if(type == Type.PUBLIC) + paneName = "Public (Visible to Collections)"; + else if(type == Type.CONNECTION_TABLE) + paneName = "Flow Connections"; + + NodeEntry nodeEntry = new NodeEntry(node, paneName); + + if(type == Type.SELF) { + BasePropertiesPane pane = new BasePropertiesPane(inspector,paneName, node); + propertiesPanes.put(nodeEntry, pane); + Collection<Property> properties = node.getSelfProperties().values(); + + pane.addDetails(properties); + } + else if(type == Type.CHILDREN) { // TODO: Deal with Groups!!!!! + CollectionPropertiesPane pane = new CollectionPropertiesPane(inspector, paneName, (VisCollection)node, type); + propertiesPanes.put(nodeEntry, pane); + pane.addTabDetails("variable"); + } + else if(type == Type.TABLE) { + CollectionPropertiesPane pane = new CollectionPropertiesPane(inspector, paneName, (VisCollection)node, type); + propertiesPanes.put(nodeEntry, pane); + pane.addExtendedTable("full tabular structure"); + } else if(type == Type.GROUP) { + GroupPropertiesPane pane = new GroupPropertiesPane(inspector, paneName, (VisGroup)node, type); + propertiesPanes.put(nodeEntry, pane); + pane.addTabDetails(); + } else if(type == Type.PUBLIC) { + BasePropertiesPane pane = new BasePropertiesPane(inspector, paneName, node, type); + propertiesPanes.put(nodeEntry, pane); + + ArrayList<Property> publicProperties = new ArrayList<>(); + ArrayList<Property> allProperties = new ArrayList<>(); + ((VisGroup)node).splitProperties(publicProperties, allProperties); + pane.addDetails(publicProperties); + + addVisibilityListeners(allProperties, pane, (VisGroup)node); + } else if(type == Type.CONNECTION_TABLE) { + // TODO:... + FlowConnectionsPane pane = new FlowConnectionsPane(inspector, paneName, (LineConnectedCollection)node, type); + propertiesPanes.put(nodeEntry, pane); + + Collection<Property> properties = new ArrayList<>(); + properties.add(((LineConnectedCollection)node).coloringScheme); + properties.add(((LineConnectedCollection)node).flowPaint); + properties.add(((LineConnectedCollection)node).flowOpacity); + pane.addDetails(properties); + + pane.addExtendedTable("structure"); + } + } + + private void addVisibilityListeners(ArrayList<Property> properties, BasePropertiesPane pane, VisGroup group) { + ChangeListener<Boolean> listener = new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + pane.clearPane(); + ArrayList<Property> publicProperties = new ArrayList<>(); + ArrayList<Property> allProperties = new ArrayList<>(); + group.splitProperties(publicProperties, allProperties); + pane.addDetails(publicProperties); + } + }; + + for(Property property:properties) { + ((Shareable)property).getPublicProperty().addListener(listener); + } + } + + public void updateStructures() { + for(PropertiesPane pane:propertiesPanes.values()) + pane.update(); + } + + + private void clean() { + vbox.getChildren().clear(); + //propertiesPanes.clear(); + } + + public void cleanAll() { + vbox.getChildren().clear(); + propertiesPanes.clear(); + } + + private PropertiesPane getPane(Container node, Type type) { + String paneName = ""; + + if(type == Type.SELF) + paneName = "Self Properties"; + else if(type == Type.CHILDREN) + paneName = "Children Properties"; + else if(type == Type.TABLE) + paneName = "Tabular Structure"; + else if(type == Type.GROUP) + paneName = "Children Properties"; + else if(type == Type.PUBLIC) + paneName = "Public (Visible to Collections)"; + else if(type == Type.CONNECTION_TABLE) + paneName = "Flow Connections"; + + NodeEntry nodeEntry = new NodeEntry(node, paneName); + PropertiesPane pane = propertiesPanes.get(nodeEntry); + + vbox.getChildren().add(pane); + pane.refresh(); + + return pane; + } + + + class NodeEntry { + private Container node; + private String name; + + public NodeEntry(Container node, String name) { + this.node = node; + this.name = name; + } + + public Container getNode() { + return node; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof NodeEntry) { + NodeEntry entry = (NodeEntry)obj; + + return entry.node == this.node && entry.name.equals(this.name); + } + else return false; + } + + @Override + public int hashCode() { + return name.hashCode() + 31*node.hashCode(); + } + + + } + + + + + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/PropertiesTable.java b/src/fr/inria/structgraphics/ui/inspector/PropertiesTable.java new file mode 100644 index 0000000000000000000000000000000000000000..1b7d3d931a3ea20b09b3e735ca2701eccd0448e2 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/PropertiesTable.java @@ -0,0 +1,136 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; +import java.util.Map; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.viscanvas.groupings.CollectionPropertyStructure; +import javafx.scene.Node; + +public class PropertiesTable extends ArrayList<PropertyDetailColumn> implements Draggable { + + private String name = null; + private CollectionPropertyStructure structure = null; + protected boolean full = false; + private Node handle; + protected Map<PropertyName, FlexibleListProperty> props; + protected Container node; + + public PropertiesTable(String name, CollectionPropertyStructure structure, Container node) { + this(name, structure, false, node); + } + + public PropertiesTable(String name, CollectionPropertyStructure structure, boolean full, Container node) { + this(name, node); + this.structure = structure; + this.full = full; + + props = full ? structure.getFullVariableList() : structure.getVariableList(); + addEntries(props); + } + + public PropertiesTable(String name, Container node) { + this.name = name; + this.node = node; + } + + public String getName() { + return name; + } + + public FlexibleListProperty getProperties(PropertyName col){ + return props.get(col);//structure.getProperties().get(col); + } + + protected void addEntries(Map<PropertyName, FlexibleListProperty> tabproperties) { + for(FlexibleListProperty properties: tabproperties.values()) { + PropertyDetailColumn column = new PropertyDetailColumn(this, properties, new PropertyName(properties.getName()), node); + column.add(properties); + add(column); + } + } + + public void refresh() { + clear(); + props = full ? structure.getFullVariableList() : structure.getVariableList(); + addEntries(props); + } + + @Override + public int getColumnsNum() { + int counter = 0; + + for(PropertyDetailColumn column:this) { + counter += column.getColumnsNum(); + } + + return counter; + } + + @Override + public int getRowsNum() { + if(isEmpty()) return 0; + else return get(0).size() + 1; + } + + public void setHandle(Node node) { + this.handle = node; + } + + @Override + public Node getNode() { + return handle; + } + + @Override + public Type getType() { + return Type.Table; + } + + @Override + public Object getPropertiesContent() { + return props.values(); + } + + @Override + public void update() { + for(PropertyDetailColumn column:this) + column.update(); + } + + @Override + public PropertyName getID() { + // TODO Auto-generated method stub + return new PropertyName(""); + } + + @Override + public VisBody getTopGroup() { + return node.getRootVirtualGroup(); + } + + @Override + public VisBody getGroup() { + return node.getVirtualGroup(); + } + + @Override + public Container getContainer() { + return node; + } + + @Override + public boolean isInWide() { + return !full; + } + + @Override + public boolean isNetwork() { + return false; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/PropertyDetail.java b/src/fr/inria/structgraphics/ui/inspector/PropertyDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..0927815e3239a75f57adc30f1e1be19d3f76e9b2 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/PropertyDetail.java @@ -0,0 +1,582 @@ +package fr.inria.structgraphics.ui.inspector; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.control.Slider; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; +import javafx.util.StringConverter; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.ColoringSchemeProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.LineTypeProperty; +import fr.inria.structgraphics.types.OpacityProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.RotationProperty; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.types.StrokeWidthProperty; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.inspector.detailmodel.SimpleSerializer; +import fr.inria.structgraphics.ui.inspector.util.TooltipCreator; + +public class PropertyDetail implements Draggable { + + public enum EditionType { + COMBO, SLIDER, COLOR_PICKER, TEXT, NONE + }; + + private List<Property> properties; + public Label label; + private List<SimpleSerializer> serializers = new ArrayList<>(); + + public HBox valueLabel = new HBox(4); + private Node graphic_unlock, graphic_lock; + public Label lockIcon = null; + + private double min, max; + + private EditionType editionType = EditionType.TEXT; + + private Object[] validItems; // TODO: Not sure if used for any type of data. Let's see. + + private Node[] fields; + private Label tmplabel; + private int tmpindex; + + private boolean tabular = false; + private boolean publicPane = false; + + private PropertyName id = null; + private Container node; + private Mark temporaryFocus = null; + + public PropertyDetail(Property property, boolean tabular, boolean publicPane, PropertyName id, Container node) { + this.tabular = tabular; + this.publicPane = publicPane; + + this.node = node; + this.id = id; + label = new Label(tabular ? "" : property.getName() + ":"); + label.setContentDisplay(ContentDisplay.LEFT); + + if(property instanceof FlexibleListProperty) { + properties = ((FlexibleListProperty)property).flatten(); + + for(Property prop: properties) { + valueLabel.getChildren().add(new Label()); + serializers.add(new SimpleSerializer(prop)); + } + } else { + valueLabel.getChildren().add(new Label()); + properties = new ArrayList<>(); + properties.add(property); + serializers.add(new SimpleSerializer(property)); + } + + // TODO + editionType = serializers.get(0).getEditionType(); + + // TODO + Object val = properties.get(0).getValue(); + if(val instanceof RefX) { + setValidItems(RefX.values()); + } else if(val instanceof RefY) { + setValidItems(RefY.values()); + } else if(val instanceof YSticky) { + setValidItems(YSticky.values()); + } else if(val instanceof XSticky) { + setValidItems(XSticky.values()); + } else if(val instanceof Constraint) { + setValidItems(Constraint.values()); + } else if(val instanceof ShapeProperty.Type) { + setValidItems(ShapeProperty.Type.values()); + } else if(val instanceof LineTypeProperty.Type) { + setValidItems(LineTypeProperty.Type.values()); + } else if(val instanceof ColoringSchemeProperty.Scheme) { + setValidItems(ColoringSchemeProperty.Scheme.values()); + } else if(properties.get(0) instanceof OpacityProperty) { + setMinMax(0, 1); + } else if(properties.get(0) instanceof RotationProperty) { + setMinMax(0, 360); + } else if(properties.get(0) instanceof StrokeWidthProperty) { + setMinMax(0, 8); + } + + updateLabels(); + initInteractors(); + + if(property instanceof Shareable) { + label.setDisable(!((Shareable)property).getActiveProperty().getValue()); + valueLabel.setDisable(!((Shareable)property).getActiveProperty().getValue()); + + ((Shareable)property).getActiveProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + label.setDisable(!((Shareable)property).getActiveProperty().getValue()); + valueLabel.setDisable(!((Shareable)property).getActiveProperty().getValue()); + } + }); + } + + if(publicPane) { + // label.setStyle("-fx-text-fill: #FF8C00;"); + } + + + } + + public PropertyDetail(Property property, boolean tabular, PropertyName id, Container node) { + this(property, tabular, false, id, node); + } + + public PropertyDetail(Property property, PropertyName id, Container node) { + this(property, false, false, id, node); + } + + + void updateLabel(Label label, Property property) { + final Node graphic; + + if(editionType == EditionType.COLOR_PICKER) { + graphic = new Rectangle(20, 10); + ((Rectangle)graphic).setFill((Color)property.getValue()); + + property.addListener(new ChangeListener<Color>() { + @Override + public void changed(ObservableValue<? extends Color> observable, Color oldValue, Color newValue) { + ((Rectangle)graphic).setFill(newValue); + } + }); + } else if(property instanceof ShapeProperty) { + graphic = new ImageView(DisplayUtils.getIcon(property.getValue().toString())); + property.addListener(new ChangeListener<ShapeProperty.Type>() { + @Override + public void changed(ObservableValue<? extends ShapeProperty.Type> observable, ShapeProperty.Type oldValue, ShapeProperty.Type newValue) { + ((ImageView)graphic).setImage(DisplayUtils.getIcon(property.getValue().toString())); + } + }); + } else if(property instanceof LineTypeProperty) { + graphic = new ImageView(DisplayUtils.getIcon(property.getValue().toString())); + property.addListener(new ChangeListener<LineTypeProperty.Type>() { + @Override + public void changed(ObservableValue<? extends LineTypeProperty.Type> observable, LineTypeProperty.Type oldValue, LineTypeProperty.Type newValue) { + ((ImageView)graphic).setImage(DisplayUtils.getIcon(property.getValue().toString())); + + Object owner = property.getBean(); + // TODO: Need to update the inspector!!!!! + if(owner instanceof VisCollection) { + ((VisFrame)((VisCollection)owner).getRoot()).getInspector().updateTreeViewLabel((VisCollection)owner); + } + } + }); + } + else graphic = null; + + /// TODO: This has changed recently. It's hopefully correct!!!! + StringConverter converter = null; + if(property.getValue() instanceof Color) + converter = new ColorStringConverter(); + else if(property.getValue() instanceof RefX) + converter = new RefXStringConverter(); + else if(property.getValue() instanceof RefY) + converter = new RefYStringConverter(); + else if(property.getValue() instanceof YSticky) + converter = new XAlignmentStringConverter(); + else if(property.getValue() instanceof XSticky) + converter = new YAlignmentStringConverter(); + else if(property.getValue() instanceof Constraint) + converter = new DistributionStringConverter(); + else if(property instanceof FlexibleListProperty) // TODO: Here, I need to take care of multiple value labels + converter = new ListStringConverter(); + else if(property instanceof ShapeProperty) + converter = new ShapeStringConverter(); + else if(property instanceof LineTypeProperty) + converter = new LineTypeStringConverter(); + else if(property instanceof StrokeWidthProperty || property instanceof OpacityProperty) + converter = new DoubleStringConverter(true); + else if(property instanceof ColoringSchemeProperty) + converter = new ColoringStringConverter(); + else if(property instanceof StringProperty) + converter = null; + else converter = new DoubleStringConverter(); + + if(graphic == null) { + // TODO: This needs to change completely!!! + if(property instanceof FlexibleListProperty) label.textProperty().bind(new StringBinding() { + @Override + protected String computeValue() { + FlexibleListProperty list = (FlexibleListProperty)property; + return list.getValueAsString(); + } + }); + else if(converter!= null) Bindings.bindBidirectional(label.textProperty(), property, converter); // TODO: This to change as well + else label.textProperty().bind(property); + } else { // TODO: ... + label.setGraphic(graphic); + label.setGraphicTextGap(1.5); + } + + // Change that for tabular data + if(tabular) label.setContentDisplay(ContentDisplay.RIGHT); // TODO: ... + } + + void updateLabels() { + for(int i = 0; i < properties.size(); ++i) { + Node child = valueLabel.getChildren().get(i); + if(child instanceof Label) updateLabel((Label)valueLabel.getChildren().get(i), properties.get(i)); + } + } + + private void initInteractors() { + fields = new Node[properties.size()]; + + for(int i = 0; i < properties.size(); ++i) { + initInteractor(i, (Label)valueLabel.getChildren().get(i), properties.get(i), serializers.get(i)); + } + } + + // TODO: ..... + public void createLockIcon(boolean lock, boolean top) { + lockIcon = new Label(" "); + graphic_unlock = new Label("\u29BB"); //ImageView(DisplayUtils.getIcon("unlinked")); + graphic_lock = top ? new Label("\u27F8") : new Label("\u27F9"); //ImageView(DisplayUtils.getIcon("linked")); + lockIcon.setGraphic(lock ? graphic_lock : graphic_unlock); + lockIcon.setGraphicTextGap(top ? 4.5 : 0); + } + + public void createExpandIcon(boolean expand) { + lockIcon = new Label(); + graphic_unlock = new Label("-"); + graphic_lock = new Label("+"); + lockIcon.setGraphic(expand ? graphic_lock : graphic_unlock); + lockIcon.setGraphicTextGap(1.5); + } + + + public void switchLock(boolean lock) { + lockIcon.setGraphic(lock ? graphic_lock : graphic_unlock); + } + + private void addGroupSharingInteractor(Label label, Property property, Mark mark) { + TooltipCreator.createTooltip(label, property.getName() + ": Press Shift to Change Visibility"); + + BooleanProperty publicProp = ((Shareable)property).getPublicProperty(); + + decorateLabel(label, publicProp.get()); + + label.setOnMouseMoved(new EventHandler<MouseEvent>(){ + @Override + public void handle(MouseEvent event) { + label.getScene().getWindow().requestFocus(); + label.requestFocus(); + } + }); + label.setOnMouseExited(new EventHandler<MouseEvent>(){ + @Override + public void handle(MouseEvent event) { + label.getParent().requestFocus(); + //label.setStyle("-fx-border-color: none;"); + } + }); + label.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent event) { + if(event.isShiftDown()) { + publicProp.set(!publicProp.get()); + + Object owner = property.getBean(); + + // TODO: Need to update the inspector!!!!! + if(owner instanceof Mark) { + VisBody virtualGroup = ((Mark)owner).getRootVirtualGroup(); + + if(virtualGroup instanceof VisCollection) { + ((VisFrame)virtualGroup.getContainer()).getInspector().updateCollectionViews((VisCollection)virtualGroup); + } + } + + //label.setStyle(publicProp.get() ? "-fx-border-color: orange;" : "-fx-border-color: none;"); + } + } + }); + + publicProp.addListener(new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + //label.setStyle(publicProp.get() ? "-fx-border-color: #FF8C00;" : "-fx-border-color: none;"); + + decorateLabel(label, publicProp.get()); + //VisGroup topGroup = ((VisGroup)mark.getContainer()).getTopGroup(); + //ArrayList<Property> bindingsgroup = topGroup.getChildPropertyStructure().getBindingsOf(property); + + } + }); + + } + + private void decorateLabel(Label label, boolean flag) { + if(!label.getText().isEmpty()) label.setUnderline(flag); + else label.setStyle(flag ? "-fx-border-color: darkgrey; -fx-border-width:1.5px;" : "-fx-border-color: none;"); + + } + + + private void initInteractor(int num, Label label, Property property, SimpleSerializer serializer) { + Object bean = property.getBean(); + if(!publicPane && bean instanceof Mark && property instanceof Shareable && !(node instanceof VisCollection) && + (bean instanceof VisGroup || ((Mark)bean).getContainer() instanceof VisGroup)){ + addGroupSharingInteractor(label, property, (Mark)bean); + } + + final Control control; + switch (editionType) { + case COMBO: { + control = new ComboBox<Object>(); + control.getStyleClass().add("detail-field"); + ((ComboBox<Object>)control).getSelectionModel().selectedItemProperty().addListener((o, oldValue, newValue) -> { + if (newValue != null && !newValue.equals(property.getValue())) { + serializer.setValue(newValue.toString()); + //updateLabel(); + recover(); + } + }); + + control.setOnMouseClicked(ev -> { + if (ev.isSecondaryButtonDown()) { + recover(); + } + }); + + break; + } + case SLIDER: { + control = new Slider(); + control.getStyleClass().add("detail-field"); + ((Slider)control).valueProperty().addListener((o, oldValue, newValue) -> { + serializer.setValue(newValue.toString()); + //updateLabel(); + }); + + break; + } + case COLOR_PICKER: { + control = new ColorPicker(); + control.getStyleClass().add("detail-field"); + ((ColorPicker)control).valueProperty().addListener((o, oldValue, newValue) -> { + serializer.setValue(newValue.toString()); + ///updateLabels(); + }); + + break; + } + default: { + control = new TextField(); // TODO: Enable a drag-based text-field modifier.... + + control.setPrefWidth(50); + control.getStyleClass().add("detail-field"); + + ((TextField)control).setOnAction(ev -> { + if (serializer != null) { + serializer.setValue(((TextField)control).getText()); + } + + //updateLabel(); + recover(); + }); + } + } + + PropertyDetail.this.fields[num] = control; + + + label.setOnMouseClicked(event -> { // TODO: ... + + Object container = property.getBean(); + if(container != null && container instanceof Mark && !((Mark)container).isHighlighted()) { + temporaryFocus = ((Mark)container); + temporaryFocus.setHighlight(true, true); + } + + if (BasePropertiesPane.activeDetail != null) { + BasePropertiesPane.activeDetail.recover(); + } + + switch (editionType) { + case COMBO: { + ((ComboBox<Object>)control).setItems(FXCollections.observableArrayList(validItems)); + ((ComboBox<Object>)control).getSelectionModel().select(property.getValue()); + break; + } + case SLIDER: { + ((Slider)control).setMax(max); + ((Slider)control).setMin(min); + ((Slider)control).setValue((double)property.getValue()); + break; + } + case COLOR_PICKER: { + ((ColorPicker)control).setValue((Color)property.getValue()); + break; + } + default: { + ((TextField)control).setText(property.getValue() +""); + break; + } + } + + // final HBox group = (HBox) label.getParent(); // TODO: Needs to change or not? Possibly not... + // group.getChildren().clear(); + // group.getChildren().add(PropertyDetail.this.field); + + valueLabel.getChildren().remove(num); + valueLabel.getChildren().add(num, PropertyDetail.this.fields[num]); + + PropertyDetail.this.fields[num].requestFocus(); + PropertyDetail.this.tmplabel = label; + PropertyDetail.this.tmpindex = num; + BasePropertiesPane.activeDetail = PropertyDetail.this; + + + // addTooltip(control, property.getName()); + + + }); + } + + public void recover() { + if(tmplabel != null) { + valueLabel.getChildren().remove(tmpindex); + valueLabel.getChildren().add(tmpindex, tmplabel); + tmplabel = null; + } + + if(temporaryFocus != null && temporaryFocus.isHighlighted()) { + temporaryFocus.setHighlight(false, true); + temporaryFocus = null; + } + + BasePropertiesPane.activeDetail = null; + } + + public void setValidItems(final Object[] validItems) { + this.validItems = validItems; + } + + public void setMinMax(final double min, final double max) { + this.min = min; + this.max = max; + } + + public void refresh() { + updateLabels(); + + serializers.clear(); + for(Property prop: properties) { + serializers.add(new SimpleSerializer(prop)); + } + + } + + @Override + public Node getNode() { + return label; + } + + @Override + public Type getType() { + if(!(node instanceof VisBody) || !getGroup().getChildPropertyStructure().getProperties().containsKey(new PropertyName(properties.get(0).getName()))) + return Type.Value; + return Type.Column; + } + + + @Override + public int getColumnsNum() { + return properties.size(); + } + + @Override + public int getRowsNum() { + if(getType() == Type.Value) return 2; + else return ((VisBody)node).getComponents().size() + 1; + } + + @Override + public Object getPropertiesContent() { // TODO!!!! This need to change to deal with multiple columns!!!!! + return FlexibleListProperty.createList(properties.get(0).getBean(), properties); + } + + @Override + public void update() { + // TODO Auto-generated method stub + updateLabels(); + } + + @Override + public PropertyName getID() { + return id; + } + + @Override + public VisBody getTopGroup() { + return node.getRootVirtualGroup(); + } + + @Override + public VisBody getGroup() { + return node.getVirtualGroup(); + } + + @Override + public Container getContainer() { + return node; + } + + @Override + public boolean isInWide() { + return true; + } + + @Override + public boolean isNetwork() { + return false; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/PropertyDetailColumn.java b/src/fr/inria/structgraphics/ui/inspector/PropertyDetailColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..6eb37223411f96c04f828a1f30de69c51f67eb7d --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/PropertyDetailColumn.java @@ -0,0 +1,111 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.Draggable; +import javafx.beans.property.ListProperty; +import javafx.beans.property.Property; +import javafx.scene.Node; +import javafx.scene.control.Label; + +public class PropertyDetailColumn extends ArrayList<PropertyDetail> implements Draggable { + + protected FlexibleListProperty propList; + protected Node handle; + protected PropertyName col; + protected PropertiesTable table; + + private Container node; + + public PropertyDetailColumn(PropertiesTable table, FlexibleListProperty properties, PropertyName col, Container node) { + this.propList = properties; + this.col = col; + this.table = table; + + this.node = node; + } + + public void add(ListProperty<Property> properties) { + for(Property property: properties) { + if(property != null) add(new PropertyDetail(property, true, col, node)); + } + } + + public String getName() { + //return propList.getName(); + return col.toString(); + } + + @Override + public Node getNode() { + return handle; + } + + @Override + public Type getType() { + return Type.Column; + } + + @Override + public Object getPropertiesContent() { + return table.getProperties(col); + } + + @Override + public int getColumnsNum() { + int length = 1; + for(PropertyDetail detail:this) + length = Math.max(length, detail.getColumnsNum()); + + return length; + } + + @Override + public int getRowsNum() { + return size() + 1; + } + + @Override + public void update() { + for(PropertyDetail detail:this) + detail.updateLabels(); + } + + public void setHandle(Label label) { + this.handle = label; + } + + @Override + public PropertyName getID() { + return col; + } + + @Override + public VisBody getTopGroup() { + return node.getRootVirtualGroup(); + } + + @Override + public VisBody getGroup() { + return node.getVirtualGroup(); + } + + @Override + public Container getContainer() { + return node; + } + + @Override + public boolean isInWide() { + return table.isInWide(); + } + + @Override + public boolean isNetwork() { + return table.isNetwork(); + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/RefXStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/RefXStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..795dd2549ab7ed5975f984be179b360d4ef3cbae --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/RefXStringConverter.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; + +public class RefXStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("right")) return RefX.Right; + else if(string.equalsIgnoreCase("center")) return RefX.Center; + else return RefX.Left; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/RefYStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/RefYStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..2b5f7ff85ea7f8fe6d83c76c80186bc5fcf5a322 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/RefYStringConverter.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; + +public class RefYStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("top")) return RefY.Top; + else if(string.equalsIgnoreCase("center")) return RefY.Center; + else return RefY.Bottom; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/ShapeStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/ShapeStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..f188d2764793cd535afd8b562f7ff77b32a0d325 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/ShapeStringConverter.java @@ -0,0 +1,14 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.types.ShapeProperty.Type; + +public class ShapeStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("triangle")) return Type.Triangle; + else if(string.equalsIgnoreCase("ellipse")) return Type.Ellipse; + else return Type.Rectangle; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/SyntaxTab.java b/src/fr/inria/structgraphics/ui/inspector/SyntaxTab.java new file mode 100644 index 0000000000000000000000000000000000000000..c2137d527cddff677f7ae170df03c1617ad02017 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/SyntaxTab.java @@ -0,0 +1,61 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.Container; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TextArea; +import javafx.scene.layout.VBox; + +public class SyntaxTab extends Tab { + + public static final String TAB_NAME = "Syntax"; + private InspectorView inspector; + + private Container activeNode = null; + + private VBox vbox; + private TextArea textArea; + + public SyntaxTab(InspectorView inspector) { + super(TAB_NAME); + + this.inspector = inspector; + + // ScrollPane scrollPane = new ScrollPane(); + // scrollPane.setFitToWidth(true); + + textArea = new TextArea(); + textArea.prefRowCountProperty().set(100); + textArea.setEditable(false); + + // vbox = new VBox(); + // vbox.getChildren().add(textArea); + // vbox.setFillWidth(true); + //scrollPane.setContent(textArea); + + setContent(textArea); + setClosable(false); + } + + + public void setActiveNode(Container node) { // TODO: ... + if(activeNode == node) return; + else clean(); + + } + + + private void createPane(Container node) { + + } + + + private void clean() { + vbox.getChildren().clear(); + } + + public void cleanAll() { + vbox.getChildren().clear(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/TabularPropertiesGrid.java b/src/fr/inria/structgraphics/ui/inspector/TabularPropertiesGrid.java new file mode 100644 index 0000000000000000000000000000000000000000..14eb5dac158e7696e0492597cf7ea7d8f2323dcf --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/TabularPropertiesGrid.java @@ -0,0 +1,85 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.ui.DragManager; +import javafx.geometry.HPos; +import javafx.geometry.VPos; +import javafx.scene.Group; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; + + +public class TabularPropertiesGrid extends GridPane { + + static final String DETAIL_LABEL_STYLE = "detail-label"; + + private PropertiesTable table; + + public TabularPropertiesGrid(boolean nested) { + if(nested) getStyleClass().add("nested-detail-pane"); + // getStyleClass().add("detail-grid"); + + setOnMousePressed(event -> { + if(BasePropertiesPane.activeDetail != null) BasePropertiesPane.activeDetail.recover(); + }); + + setHgap(10); + setVgap(2); + setSnapToPixel(true); + } + + protected void addPropertiesTable(PropertiesTable table) { + this.table = table; + + table.setHandle(this); + DragManager.getSingleton().observe(table); + + refreshGrid(); + } + + public PropertiesTable getTable() { + return table; + } + + + public void refreshGrid() { + getChildren().clear(); + + int j = 1; + for(PropertyDetailColumn column: table) { + int i = 0; + + final Label label = new Label(column.getName()); // I probably need to add some listeners here!!! + column.setHandle(label); + DragManager.getSingleton().observe(column); + + label.getStyleClass().add("detail-label"); + GridPane.setHalignment(label, HPos.CENTER); + GridPane.setValignment(label, VPos.TOP); + add(label, j, i++); + + for(PropertyDetail detail: column) { + detail.valueLabel.getStyleClass().add("detail-value"); + final Group group = new Group(detail.valueLabel); + GridPane.setHalignment(group, HPos.RIGHT); + add(group, j, i++); + } + j++; + } + } + + + protected void clearPane() { + getChildren().clear(); + //details.clear(); + // TODO.... + } + + public void refresh() { + /* + for (int i = 0; i < details.size(); i++) { + details.get(i).refresh(); + }*/ + } + + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/TitledIndividualPropertiesPane.java b/src/fr/inria/structgraphics/ui/inspector/TitledIndividualPropertiesPane.java new file mode 100644 index 0000000000000000000000000000000000000000..9da7e5ec499207ea8ff027de3f9decce4583784f --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/TitledIndividualPropertiesPane.java @@ -0,0 +1,56 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.Collection; +import java.util.List; +import java.util.SortedSet; + +import fr.inria.structgraphics.types.PropertyName; +import javafx.beans.property.Property; +import javafx.scene.Node; +import javafx.scene.control.TitledPane; + + +public class TitledIndividualPropertiesPane extends TitledPane { + + static final String DETAIL_LABEL_STYLE = "detail-label"; + private IndividualPropertiesGrid gridPane; + + public TitledIndividualPropertiesPane(String name, IndividualPropertiesGrid gridPane) { + super(name, null); + + this.gridPane = gridPane; + + setOnMousePressed(event -> { + if(BasePropertiesPane.activeDetail != null) BasePropertiesPane.activeDetail.recover(); + }); + + gridPane.setHgap(10); + gridPane.setVgap(2); + gridPane.setSnapToPixel(true); + + setContent(gridPane); + setCollapsible(false); + } + + protected void addToPane(final Node... nodes) { + gridPane.addToPane(nodes); + } + + protected void clearPane() { + gridPane.clearPane(); + } + + public void addDetails(final Collection<Property> properties) { + gridPane.addDetails(properties); + } + + + public void addDetails(final Collection<Property> properties, SortedSet<PropertyName> common) { + gridPane.addDetails(properties, common); + } + + public void refresh() { + gridPane.refresh(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/TitledTablePane.java b/src/fr/inria/structgraphics/ui/inspector/TitledTablePane.java new file mode 100644 index 0000000000000000000000000000000000000000..33d1886dd862b0bc878c43f4906bbf5a71addf0b --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/TitledTablePane.java @@ -0,0 +1,55 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.ui.DragManager; +import javafx.scene.control.TitledPane; + +public class TitledTablePane extends TitledPane { + + static final String DETAIL_LABEL_STYLE = "detail-label"; + private TabularPropertiesGrid gridPane; + + public TitledTablePane(String name, TabularPropertiesGrid gridPane) { + super(name, null); + + getStyleClass().add("titled-table-pane"); + + this.gridPane = gridPane; + + setOnMousePressed(event -> { + if(BasePropertiesPane.activeDetail != null) BasePropertiesPane.activeDetail.recover(); + }); + + gridPane.setHgap(10); + gridPane.setVgap(2); + gridPane.setSnapToPixel(true); + + setContent(gridPane); + setCollapsible(false); + } + + protected void addPropertiesTable(PropertiesTable table) { + gridPane.addPropertiesTable(table); + + table.setHandle(this); + DragManager.getSingleton().observe(table); + } + + public PropertiesTable getTable() { + return gridPane.getTable(); + } + + + public void refreshGrid() { + gridPane.refreshGrid(); + } + + + protected void clearPane() { + gridPane.clearPane(); + } + + public void refresh() { + gridPane.refresh(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/VisualizationNode.java b/src/fr/inria/structgraphics/ui/inspector/VisualizationNode.java new file mode 100644 index 0000000000000000000000000000000000000000..1d3db351cec054ff6b63d7e0de56529a0295ff7c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/VisualizationNode.java @@ -0,0 +1,48 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import javafx.scene.image.Image; + +public class VisualizationNode { + private Container container; + + public VisualizationNode(Container container) { + this.container = container; + } + + public String toString() { + return getName(); + } + + public Image getIcon() { + return null; + } + + public Container getNode() { + return container; + } + + public String getName() { + return (container instanceof Mark) ? container.getName() + " " + container.id.get() : container.getName(); + } + + public String getType() { + return container.getType(); + } + + public boolean isVisible() { + return true; + } + + public boolean isFocused() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof VisualizationNode && container == ((VisualizationNode)obj).container); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/VisualizationTreeView.java b/src/fr/inria/structgraphics/ui/inspector/VisualizationTreeView.java new file mode 100644 index 0000000000000000000000000000000000000000..c77aae69a6abd5024c07d12f935498d326048080 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/VisualizationTreeView.java @@ -0,0 +1,168 @@ +package fr.inria.structgraphics.ui.inspector; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.GroupLayout.SequentialGroup; + +import org.matheclipse.core.builtin.function.Set; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.ShapeProperty.Type; +import fr.inria.structgraphics.ui.tools.SelectTool; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TreeCell; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; +import javafx.scene.image.ImageView; + +public class VisualizationTreeView extends TreeView<VisualizationNode> { + + private InspectorView inspector; + private Map<Container, TreeItem<VisualizationNode>> map; + private SelectTool selectTool; + + public VisualizationTreeView(InspectorView inspector) { + this.inspector = inspector; + map = new HashMap<>(); + + getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + setCellFactory(node -> new TreeCell<VisualizationNode>() { + @Override public void updateItem(final VisualizationNode item, final boolean empty) { + super.updateItem(item, empty); + + TreeItem<VisualizationNode> treeItem = getTreeItem(); + setGraphic(treeItem == null ? null : treeItem.getGraphic()); + setText(item == null ? null : item.toString()); + setOpacity(1); + } + }); + + getSelectionModel().getSelectedIndices().addListener(new ListChangeListener<Integer>() { + @Override + public void onChanged(Change<? extends Integer> c) { // TODO: ... + ObservableList<TreeItem<VisualizationNode>> selected = getSelectionModel().getSelectedItems(); +// TreeItem<VisualizationNode> selected = getSelectionModel().getSelectedItem(); + + if(selected !=null && !selected.isEmpty()) { + TreeItem<VisualizationNode> last = selected.get(selected.size() - 1); + if(last != null) inspector.getPropertiesTable().setActiveNode(last.getValue().getNode()); + + if(selectTool != null && selected.get(0).getValue() != null) { + selectTool.setActive(true); + Container node = selected.get(0).getValue().getNode(); + if(node != null && node instanceof Mark) selectTool.select((Mark)node, this, selected.size() < 2); + for(int i = 1; i < selected.size(); ++i) { + TreeItem<VisualizationNode> item = selected.get(i); + if(item.getValue().getNode() instanceof Mark) selectTool.addSelection((Mark)item.getValue().getNode(), this); + } + } + } else if(selectTool != null) selectTool.select(null, this); + } + }); + + } + + public void updateLabel(Container container) { + TreeItem<VisualizationNode> item = map.get(container); + + VisualizationNode node = new VisualizationNode(container); + item.setValue(node); + item.setGraphic(new ImageView(DisplayUtils.getIcon(node))); + } + + public void update(Container container) { + map.clear(); + + setRoot(parseSubTree(container)); + refreshPropertiesTabs(container); + } + + public void updateOrder() { + SelectTool tmp = selectTool; + selectTool = null; // To not propagate back the chances to the selectTool + + update(inspector.getVisualizationFrame()); + getSelectionModel().clearSelection(); + + for(Mark mark: tmp.getSelected()) { + getSelectionModel().select(map.get(mark)); + } + selectTool = tmp; + } + + public void setActiveNodes(ArrayList<Mark> marks) { + SelectTool tmp = selectTool; + selectTool = null; // To not propagate back the chances to the selectTool + + getSelectionModel().clearSelection(); + for(Mark mark: marks) { + getSelectionModel().select(map.get(mark)); + } + + selectTool = tmp; + } + + public void setActiveNode(Mark mark) { + SelectTool tmp = selectTool; + selectTool = null; // To not propagate back the chances to the selectTool + + getSelectionModel().clearSelection(); + getSelectionModel().select(map.get(mark)); + + selectTool = tmp; + } + + private void refreshPropertiesTabs(Container parent) { + for(Mark mark:parent.getComponents()) { + refreshPropertiesTabs(mark); + } + + inspector.generateView(parent); + } + + private TreeItem<VisualizationNode> parseSubTree(Container parent) { + VisualizationNode node = new VisualizationNode(parent); + TreeItem<VisualizationNode> item = new TreeItem<VisualizationNode>(node, new ImageView(DisplayUtils.getIcon(node))); + + map.put(parent, item); + + for(Mark mark:parent.getComponents()) { + item.getChildren().add(parseSubTree(mark)); + } + + if(parent instanceof ShapeMark) { + ShapeMark mark = (ShapeMark)parent; + mark.getShapeProperty().addListener(new ChangeListener<ShapeProperty.Type>() { + @Override + public void changed(ObservableValue<? extends Type> observable, Type oldValue, Type newValue) { + item.graphicProperty().set(new ImageView(DisplayUtils.getIcon(item.getValue()))); + refresh(); + } + }); + } + + item.setExpanded(true); + + return item; + } + + public void setSelector(SelectTool selector) { + selectTool = selector; + + ContextMenu contextMenu = new ContextMenu(); + selectTool.addItems(contextMenu); + this.setContextMenu(contextMenu); + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/XAlignmentStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/XAlignmentStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..31bd538af6291b1942f2ea373c628a8dd999342e --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/XAlignmentStringConverter.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; + +public class XAlignmentStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("no")) return YSticky.No; + else return YSticky.Yes; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/YAlignmentStringConverter.java b/src/fr/inria/structgraphics/ui/inspector/YAlignmentStringConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..984c3274b9cb0bd56d26384e83eef28b6d2d3bcb --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/YAlignmentStringConverter.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.ui.inspector; + +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; + +public class YAlignmentStringConverter extends GeneralStringConverter { + + @Override + public Object fromString(String string) { + if(string.equalsIgnoreCase("no")) return XSticky.No; + else return XSticky.Yes; + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/detailmodel/SimpleSerializer.java b/src/fr/inria/structgraphics/ui/inspector/detailmodel/SimpleSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..9629ccdb8719e83e15da1f5b6e802b20336b7181 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/detailmodel/SimpleSerializer.java @@ -0,0 +1,182 @@ +/* + * Scenic View, + * Copyright (C) 2012 Jonathan Giles, Ander Ruiz, Amy Fowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package fr.inria.structgraphics.ui.inspector.detailmodel; + +import java.lang.reflect.Method; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.ColoringSchemeProperty; +import fr.inria.structgraphics.types.ConstrainedDoubleProperty; +import fr.inria.structgraphics.types.DistanceProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.LineTypeProperty; +import fr.inria.structgraphics.types.OpacityProperty; +import fr.inria.structgraphics.types.RotationProperty; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.StrokeWidthProperty; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.ui.inspector.PropertyDetail.EditionType; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.beans.property.StringProperty; +import javafx.beans.value.WritableValue; +import javafx.scene.paint.Color; + +@SuppressWarnings("rawtypes") +public class SimpleSerializer implements WritableValue<String> { + + private final Property property; + private Class<? extends Enum> enumClass; + private EditionType editionType; + /** + * This is getting a bit messy, it works for now but... + */ + private double minValue; + private double maxValue; + + public SimpleSerializer(final Property property) { + this.property = property; + if (property.getValue() instanceof RefX || property.getValue() instanceof RefY + || property.getValue() instanceof YSticky || property.getValue() instanceof XSticky + || property.getValue() instanceof Constraint || property.getValue() instanceof ColoringSchemeProperty.Scheme + || property.getValue() instanceof ShapeProperty.Type || property.getValue() instanceof LineTypeProperty.Type ) { + editionType = EditionType.COMBO; + } else if (property.getValue() instanceof Color) { + editionType = EditionType.COLOR_PICKER; + } else if(property instanceof FlexibleListProperty) { + editionType = EditionType.NONE; + } else if(property instanceof OpacityProperty /*|| property instanceof RotationProperty */|| + property instanceof StrokeWidthProperty) { + editionType = EditionType.SLIDER; + } + else { + editionType = EditionType.TEXT; + } + } + + public void setEnumClass(final Class<? extends Enum> enumClass) { + this.enumClass = enumClass; + if (enumClass != null) { + editionType = EditionType.COMBO; + } + } + + public EditionType getEditionType() { + return editionType; + } + + @Override + public String getValue() { + if (property.getValue() != null) { + return property.getValue().toString(); + } + return ""; + } + + public String[] getValidValues() { + if (enumClass != null) { + try { + final Method m = enumClass.getMethod("values"); + final Object[] values = (Object[]) m.invoke(null, (Object[]) null); + final String[] sValues = new String[values.length]; + for (int i = 0; i < sValues.length; i++) { + sValues[i] = values[i].toString(); + } + return sValues; + } catch (final Exception e) { + e.printStackTrace(); + return null; + } + } else if (property instanceof BooleanProperty) { + return new String[] { "true", "false" }; + } + return null; + } + + public double getMinValue() { + return minValue; + } + + public void setMinValue(final double minValue) { + this.minValue = minValue; + editionType = EditionType.SLIDER; + } + + public double getMaxValue() { + return maxValue; + } + + public void setMaxValue(final double maxValue) { + this.maxValue = maxValue; + editionType = EditionType.SLIDER; + } + + @SuppressWarnings("unchecked") @Override public void setValue(final String value) { + try { + if (property instanceof ConstrainedDoubleProperty) { + ((ConstrainedDoubleProperty)property).updateValue(Double.parseDouble(value)); + } + else if (property instanceof BooleanProperty) { + property.setValue(Boolean.parseBoolean(value)); + } else if (property instanceof IntegerProperty) { + property.setValue(Integer.parseInt(value)); + } else if (property instanceof DoubleProperty) { + property.setValue(Double.parseDouble(value)); + } else if (property instanceof StringProperty) { + property.setValue(value); + } else if (property instanceof ObjectProperty) { + Object val = property.getValue(); + + if (enumClass != null) { + final Method m = enumClass.getMethod("valueOf", String.class); + property.setValue(m.invoke(null, value)); + } else if (val instanceof Color) { + property.setValue(Color.valueOf(value)); + } else if(val instanceof RefX) { + property.setValue(RefX.valueOf(value)); + } else if (val instanceof RefY) { + property.setValue(RefY.valueOf(value)); + } else if(val instanceof YSticky) { + property.setValue(YSticky.valueOf(value)); + } else if (val instanceof XSticky) { + property.setValue(XSticky.valueOf(value)); + } else if(val instanceof Constraint) { + property.setValue(Constraint.valueOf(value)); + } else if (val instanceof ShapeProperty.Type) { + property.setValue(ShapeProperty.Type.valueOf(value)); + } else if (val instanceof LineTypeProperty.Type) { + property.setValue(LineTypeProperty.Type.valueOf(value)); + } else if(val instanceof ColoringSchemeProperty.Scheme) { + property.setValue(ColoringSchemeProperty.Scheme.valueOf(value)); + } + else { + throw new RuntimeException("Property type not supported"); + } + } + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Accordion.png b/src/fr/inria/structgraphics/ui/inspector/icons/Accordion.png new file mode 100644 index 0000000000000000000000000000000000000000..9aebcade82884339fa5714bb3188f46127af92ae Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Accordion.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/AnchorPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/AnchorPane.png new file mode 100644 index 0000000000000000000000000000000000000000..94877cadc6ce4edd6a28109f81cf6438086b637e Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/AnchorPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Application.png b/src/fr/inria/structgraphics/ui/inspector/icons/Application.png new file mode 100644 index 0000000000000000000000000000000000000000..f4712826f37be38ac5fb550e72858a1d2f2636e8 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Application.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Arc.png b/src/fr/inria/structgraphics/ui/inspector/icons/Arc.png new file mode 100644 index 0000000000000000000000000000000000000000..13327decab2bc9d1182eb34e79e3e7b622add59b Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Arc.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Area.png b/src/fr/inria/structgraphics/ui/inspector/icons/Area.png new file mode 100644 index 0000000000000000000000000000000000000000..063775ad44976275816e76d04905c8b415e73179 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Area.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/AreaChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/AreaChart.png new file mode 100644 index 0000000000000000000000000000000000000000..063775ad44976275816e76d04905c8b415e73179 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/AreaChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Axis.png b/src/fr/inria/structgraphics/ui/inspector/icons/Axis.png new file mode 100644 index 0000000000000000000000000000000000000000..645b1ce1703e513b03d6cb090d7ff774456a1168 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Axis.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/BarChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/BarChart.png new file mode 100644 index 0000000000000000000000000000000000000000..695eda1829142ed0003169c77e2c890fbb2c3ee4 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/BarChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/BarChart3D.png b/src/fr/inria/structgraphics/ui/inspector/icons/BarChart3D.png new file mode 100644 index 0000000000000000000000000000000000000000..ffa7f8f442d682d470d08555bb2f3305f51875df Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/BarChart3D.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/BorderPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/BorderPane.png new file mode 100644 index 0000000000000000000000000000000000000000..39145ba1016c3db1f0cecd14c4a20c9c762fe6b2 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/BorderPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/BubbleChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/BubbleChart.png new file mode 100644 index 0000000000000000000000000000000000000000..98a2631c05d5f206b273951e638d09efbadec623 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/BubbleChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Button.png b/src/fr/inria/structgraphics/ui/inspector/icons/Button.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f390b4e656591375803d1f0d99558612578a4f Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Button.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Canvas.png b/src/fr/inria/structgraphics/ui/inspector/icons/Canvas.png new file mode 100644 index 0000000000000000000000000000000000000000..9ad18e55a78ce0bfc0b7e962498861bb1b2b7c93 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Canvas.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Chart.png b/src/fr/inria/structgraphics/ui/inspector/icons/Chart.png new file mode 100644 index 0000000000000000000000000000000000000000..bd710cf94bae639904b3270547f4ff26580401ba Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Chart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/CheckBox.png b/src/fr/inria/structgraphics/ui/inspector/icons/CheckBox.png new file mode 100644 index 0000000000000000000000000000000000000000..0377ddbb0e87e29393329e316917314810b49361 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/CheckBox.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/CheckMenuItem.png b/src/fr/inria/structgraphics/ui/inspector/icons/CheckMenuItem.png new file mode 100644 index 0000000000000000000000000000000000000000..744b8d81013d6e68939af9036b5d779d5b717972 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/CheckMenuItem.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ChoiceBox.png b/src/fr/inria/structgraphics/ui/inspector/icons/ChoiceBox.png new file mode 100644 index 0000000000000000000000000000000000000000..5348c245037006e61d3cfbb24c1dd6f641387e64 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ChoiceBox.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Circle.png b/src/fr/inria/structgraphics/ui/inspector/icons/Circle.png new file mode 100644 index 0000000000000000000000000000000000000000..ea211854362d397baba26431a5637c787b187562 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Circle.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ClipView.png b/src/fr/inria/structgraphics/ui/inspector/icons/ClipView.png new file mode 100644 index 0000000000000000000000000000000000000000..4dc2fbe5b7ee3c8771ce431e52f55cbd77be7fdf Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ClipView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ClosePath.png b/src/fr/inria/structgraphics/ui/inspector/icons/ClosePath.png new file mode 100644 index 0000000000000000000000000000000000000000..9c53620f2807494797d66e05793d116d47a55dd9 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ClosePath.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Collection.png b/src/fr/inria/structgraphics/ui/inspector/icons/Collection.png new file mode 100644 index 0000000000000000000000000000000000000000..695eda1829142ed0003169c77e2c890fbb2c3ee4 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Collection.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ColorPicker.png b/src/fr/inria/structgraphics/ui/inspector/icons/ColorPicker.png new file mode 100644 index 0000000000000000000000000000000000000000..af2055673b67983f802906867781f4be76049bfc Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ColorPicker.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ComboBox.png b/src/fr/inria/structgraphics/ui/inspector/icons/ComboBox.png new file mode 100644 index 0000000000000000000000000000000000000000..f71271bbf8db00fd600ae417e842237aa229b1fd Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ComboBox.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ContextMenu.png b/src/fr/inria/structgraphics/ui/inspector/icons/ContextMenu.png new file mode 100644 index 0000000000000000000000000000000000000000..17d650939d41d18876b95354a7b05924af9b9730 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ContextMenu.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/CubicCurve.png b/src/fr/inria/structgraphics/ui/inspector/icons/CubicCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b9b3f176482e0825a5fa31b149f33908a64936 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/CubicCurve.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/CustomMenuItem.png b/src/fr/inria/structgraphics/ui/inspector/icons/CustomMenuItem.png new file mode 100644 index 0000000000000000000000000000000000000000..9a977652d34f240bb7924e78dcb81f7417552051 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/CustomMenuItem.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/CustomNode.png b/src/fr/inria/structgraphics/ui/inspector/icons/CustomNode.png new file mode 100644 index 0000000000000000000000000000000000000000..01c507417e7b9411aa6d7880910244f19650b653 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/CustomNode.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Ellipse.png b/src/fr/inria/structgraphics/ui/inspector/icons/Ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..0a31957464a693337c107cb57915836316416d85 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Ellipse.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/FXDNode.png b/src/fr/inria/structgraphics/ui/inspector/icons/FXDNode.png new file mode 100644 index 0000000000000000000000000000000000000000..613cc580e7407cf0cef501be4bfe38099f0619e2 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/FXDNode.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/FlowPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/FlowPane.png new file mode 100644 index 0000000000000000000000000000000000000000..150f7ee89b1d7d0d6de0acabcbd929dfe5346f79 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/FlowPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Gadget.png b/src/fr/inria/structgraphics/ui/inspector/icons/Gadget.png new file mode 100644 index 0000000000000000000000000000000000000000..c49432346ccfd70d317b5bb6e27a6d0fdea42e4c Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Gadget.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Graphic.png b/src/fr/inria/structgraphics/ui/inspector/icons/Graphic.png new file mode 100644 index 0000000000000000000000000000000000000000..6e44e3c7f9266f3603c740125134bc00d4bc65df Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Graphic.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/GridPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/GridPane.png new file mode 100644 index 0000000000000000000000000000000000000000..9032be1a1f1b369b4dc515f3c2ac84ecbf83a477 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/GridPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Group.png b/src/fr/inria/structgraphics/ui/inspector/icons/Group.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d35c23cd170626c88090d7fd1592c64a738f63 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Group.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/HBox.png b/src/fr/inria/structgraphics/ui/inspector/icons/HBox.png new file mode 100644 index 0000000000000000000000000000000000000000..063090b2a17464867d6ac2d2a3e6a5e507a80288 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/HBox.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/HTMLEditor.png b/src/fr/inria/structgraphics/ui/inspector/icons/HTMLEditor.png new file mode 100644 index 0000000000000000000000000000000000000000..89a1d73678209f120f9cb03e4fc57fb86159afd8 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/HTMLEditor.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Hyperlink.png b/src/fr/inria/structgraphics/ui/inspector/icons/Hyperlink.png new file mode 100644 index 0000000000000000000000000000000000000000..2928c034b5c17685035ed291487e50c20a058bd7 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Hyperlink.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ImageView.png b/src/fr/inria/structgraphics/ui/inspector/icons/ImageView.png new file mode 100644 index 0000000000000000000000000000000000000000..79f440e51e5c0a0a07978608e25e785b525a718c Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ImageView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Java.png b/src/fr/inria/structgraphics/ui/inspector/icons/Java.png new file mode 100644 index 0000000000000000000000000000000000000000..4fa8fabaad2c344d893d5ce59cc19d6333e2b304 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Java.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Label.png b/src/fr/inria/structgraphics/ui/inspector/icons/Label.png new file mode 100644 index 0000000000000000000000000000000000000000..5face408ddb5d0d88f6cee22a76fbb0039f5020a Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Label.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Line.png b/src/fr/inria/structgraphics/ui/inspector/icons/Line.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cfda1c47aef2d39660af65e00e7d848960c082 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Line.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/LineChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/LineChart.png new file mode 100644 index 0000000000000000000000000000000000000000..3b5a218fcb8e6d95da3aa14df954cbe2b61e8986 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/LineChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ListView.png b/src/fr/inria/structgraphics/ui/inspector/icons/ListView.png new file mode 100644 index 0000000000000000000000000000000000000000..1879bc6dbcdbaca9ef2d6435bc91f0acea869461 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ListView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/LockIcon.png b/src/fr/inria/structgraphics/ui/inspector/icons/LockIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..74add8c05d955dce60c822db2fc84c0b0661af07 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/LockIcon.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/MediaView.png b/src/fr/inria/structgraphics/ui/inspector/icons/MediaView.png new file mode 100644 index 0000000000000000000000000000000000000000..86180bdefc0155bc759a717f0524a2b79d2bf298 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/MediaView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Menu.png b/src/fr/inria/structgraphics/ui/inspector/icons/Menu.png new file mode 100644 index 0000000000000000000000000000000000000000..84cfc162de1a39ad9da305c7996010c64bcd841d Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Menu.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/MenuBar.png b/src/fr/inria/structgraphics/ui/inspector/icons/MenuBar.png new file mode 100644 index 0000000000000000000000000000000000000000..99d6389b795b6a2cb528b05966fbfeede105b5b7 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/MenuBar.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/MenuButton.png b/src/fr/inria/structgraphics/ui/inspector/icons/MenuButton.png new file mode 100644 index 0000000000000000000000000000000000000000..037973ad4dff8e98c2a4201c7fd6da7aa91426e3 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/MenuButton.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/MenuItem.png b/src/fr/inria/structgraphics/ui/inspector/icons/MenuItem.png new file mode 100644 index 0000000000000000000000000000000000000000..81246b88ba1c02a3b0b30e00c2b9921f3391d914 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/MenuItem.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/None.png b/src/fr/inria/structgraphics/ui/inspector/icons/None.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b4b0054f31605eabfe1d4b9cccaa487e4b3402 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/None.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/NumberAxis.png b/src/fr/inria/structgraphics/ui/inspector/icons/NumberAxis.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb97343eb6de53d8b3f4fc35f468704f01d9b84 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/NumberAxis.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Pane.png b/src/fr/inria/structgraphics/ui/inspector/icons/Pane.png new file mode 100644 index 0000000000000000000000000000000000000000..a61a33e4d3e978e82beb2422b4ad1bdb143ed473 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Pane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Panel.png b/src/fr/inria/structgraphics/ui/inspector/icons/Panel.png new file mode 100644 index 0000000000000000000000000000000000000000..8683c4ac6cc1dd81b8e4f50cf75db7c9b3d605f1 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Panel.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/PasswordField.png b/src/fr/inria/structgraphics/ui/inspector/icons/PasswordField.png new file mode 100644 index 0000000000000000000000000000000000000000..12af54fc000d2911c9879dabea5996ca97138390 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/PasswordField.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Path.png b/src/fr/inria/structgraphics/ui/inspector/icons/Path.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cfda1c47aef2d39660af65e00e7d848960c082 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Path.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/PieChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/PieChart.png new file mode 100644 index 0000000000000000000000000000000000000000..43f8fa4e69b7664c9acd43fe6ed8ccb0cfada2ca Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/PieChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/PieChart3D.png b/src/fr/inria/structgraphics/ui/inspector/icons/PieChart3D.png new file mode 100644 index 0000000000000000000000000000000000000000..d9534bac39323c0c291d7a12c86462f1f0323a31 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/PieChart3D.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Polygon.png b/src/fr/inria/structgraphics/ui/inspector/icons/Polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b8e7973088eeb0093fbb56af472e4084be379f Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Polygon.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Polyline.png b/src/fr/inria/structgraphics/ui/inspector/icons/Polyline.png new file mode 100644 index 0000000000000000000000000000000000000000..53b26258868417b015ea879d6a9ec52242b72224 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Polyline.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Popup.png b/src/fr/inria/structgraphics/ui/inspector/icons/Popup.png new file mode 100644 index 0000000000000000000000000000000000000000..e6890205099db9f4a92b9ba7c08d93826be78e51 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Popup.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ProgressBar.png b/src/fr/inria/structgraphics/ui/inspector/icons/ProgressBar.png new file mode 100644 index 0000000000000000000000000000000000000000..f58030b6c3ef71c623a59862253ac52b250cc16d Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ProgressBar.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ProgressIndicator.png b/src/fr/inria/structgraphics/ui/inspector/icons/ProgressIndicator.png new file mode 100644 index 0000000000000000000000000000000000000000..7135f5d1a6d5d37c3b87ee5d6781db90f7b52c45 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ProgressIndicator.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/QuadCurve.png b/src/fr/inria/structgraphics/ui/inspector/icons/QuadCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..47c2a25034a27cb9b051f8f9dfb6bbf2581f5b60 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/QuadCurve.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/RadioButton.png b/src/fr/inria/structgraphics/ui/inspector/icons/RadioButton.png new file mode 100644 index 0000000000000000000000000000000000000000..305dfcedf442157bb16abb27cabc8e47ac01d56b Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/RadioButton.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/RadioMenuItem.png b/src/fr/inria/structgraphics/ui/inspector/icons/RadioMenuItem.png new file mode 100644 index 0000000000000000000000000000000000000000..c52ac5230bc4c6634a8ee05d3ff9ebaecb0f0ba8 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/RadioMenuItem.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Rectangle.png b/src/fr/inria/structgraphics/ui/inspector/icons/Rectangle.png new file mode 100644 index 0000000000000000000000000000000000000000..72abc0e13ae9fbb6c70be6bb9befc4966956bf81 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Rectangle.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Region.png b/src/fr/inria/structgraphics/ui/inspector/icons/Region.png new file mode 100644 index 0000000000000000000000000000000000000000..3dedee3ab19270ded86fc8a63bc27d6980256a59 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Region.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/SVGPath.png b/src/fr/inria/structgraphics/ui/inspector/icons/SVGPath.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b9b3f176482e0825a5fa31b149f33908a64936 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/SVGPath.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ScatterChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/ScatterChart.png new file mode 100644 index 0000000000000000000000000000000000000000..e63c17028db77bc4c032dc32e501902f11efffa2 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ScatterChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ScrollBar.png b/src/fr/inria/structgraphics/ui/inspector/icons/ScrollBar.png new file mode 100644 index 0000000000000000000000000000000000000000..ba460a8d3cefad404b6a806ac5b428a8c4a53952 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ScrollBar.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ScrollPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/ScrollPane.png new file mode 100644 index 0000000000000000000000000000000000000000..3002d904f425e2102071fafd917b880bc5117074 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ScrollPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Separator.png b/src/fr/inria/structgraphics/ui/inspector/icons/Separator.png new file mode 100644 index 0000000000000000000000000000000000000000..c40a87736830d8d10157fab00eaf18802630da7b Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Separator.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/SeparatorMenuItem.png b/src/fr/inria/structgraphics/ui/inspector/icons/SeparatorMenuItem.png new file mode 100644 index 0000000000000000000000000000000000000000..22d7f1cb18f686576f6f670276a864795e3bd840 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/SeparatorMenuItem.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Slider.png b/src/fr/inria/structgraphics/ui/inspector/icons/Slider.png new file mode 100644 index 0000000000000000000000000000000000000000..78a2adbb74446d14b3cf08f7a1c7af75ca520c43 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Slider.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/SplitMenuButton.png b/src/fr/inria/structgraphics/ui/inspector/icons/SplitMenuButton.png new file mode 100644 index 0000000000000000000000000000000000000000..007ec9065f7ff8837f555b6a9719cdcc8c57973d Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/SplitMenuButton.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/SplitPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/SplitPane.png new file mode 100644 index 0000000000000000000000000000000000000000..b1b38d2aacd8ff601e688fed4575c806762065c9 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/SplitPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/SplitPane_v.png b/src/fr/inria/structgraphics/ui/inspector/icons/SplitPane_v.png new file mode 100644 index 0000000000000000000000000000000000000000..d1519a314361225347ca0e476adb53caa746779e Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/SplitPane_v.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/StackPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/StackPane.png new file mode 100644 index 0000000000000000000000000000000000000000..54a7e25625801bac2474da21f8d77a1a04dd3405 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/StackPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/StackedAreaChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/StackedAreaChart.png new file mode 100644 index 0000000000000000000000000000000000000000..ad7c057179e0330e19463195b1da5014a48584fc Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/StackedAreaChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/StackedBarChart.png b/src/fr/inria/structgraphics/ui/inspector/icons/StackedBarChart.png new file mode 100644 index 0000000000000000000000000000000000000000..01ac120109b5a5891c3e8dc257c8de1ac0b862ea Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/StackedBarChart.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Stage.png b/src/fr/inria/structgraphics/ui/inspector/icons/Stage.png new file mode 100644 index 0000000000000000000000000000000000000000..77f3e892ea2d6b1d5090454f7762744c826d18ce Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Stage.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Straight.png b/src/fr/inria/structgraphics/ui/inspector/icons/Straight.png new file mode 100644 index 0000000000000000000000000000000000000000..a3246e0e999e204c3d5d13b5c5abf74e547418c9 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Straight.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Tab.png b/src/fr/inria/structgraphics/ui/inspector/icons/Tab.png new file mode 100644 index 0000000000000000000000000000000000000000..92bb113a5099be28dc3a3a0c97a95005e6921bf8 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Tab.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TabPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/TabPane.png new file mode 100644 index 0000000000000000000000000000000000000000..3e56f636bc43af3aeb886177085205f64343c14e Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TabPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TableColumn.png b/src/fr/inria/structgraphics/ui/inspector/icons/TableColumn.png new file mode 100644 index 0000000000000000000000000000000000000000..ff6ba46f7ba0c13ccd4d0dc392b419320fe98a5a Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TableColumn.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TableRow.png b/src/fr/inria/structgraphics/ui/inspector/icons/TableRow.png new file mode 100644 index 0000000000000000000000000000000000000000..13e10c5d202a7cd1b1e4d40622fe47b08caeec88 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TableRow.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TableView.png b/src/fr/inria/structgraphics/ui/inspector/icons/TableView.png new file mode 100644 index 0000000000000000000000000000000000000000..f09f545deacafee3e0e3eb82faa776b7097693ab Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TableView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Text-1.png b/src/fr/inria/structgraphics/ui/inspector/icons/Text-1.png new file mode 100644 index 0000000000000000000000000000000000000000..69d67fb8c2ca3094278b91b8cc83b17682cdf667 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Text-1.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Text.png b/src/fr/inria/structgraphics/ui/inspector/icons/Text.png new file mode 100644 index 0000000000000000000000000000000000000000..dba9ab80f7d0163ce555628df3ccdcd15afd1dec Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Text.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TextArea.png b/src/fr/inria/structgraphics/ui/inspector/icons/TextArea.png new file mode 100644 index 0000000000000000000000000000000000000000..65b69e2462cfc2c1b97c78040116f87ebcb85a3a Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TextArea.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TextField.png b/src/fr/inria/structgraphics/ui/inspector/icons/TextField.png new file mode 100644 index 0000000000000000000000000000000000000000..0c8420afa58ea735efca5124067bb300b310a3cf Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TextField.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TilePane.png b/src/fr/inria/structgraphics/ui/inspector/icons/TilePane.png new file mode 100644 index 0000000000000000000000000000000000000000..55945b6a34f8b0a5af776a76cee1c4b325123131 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TilePane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TitledPane.png b/src/fr/inria/structgraphics/ui/inspector/icons/TitledPane.png new file mode 100644 index 0000000000000000000000000000000000000000..33a08ecb49b6e7bc7aedb10773ee13ebc97d72f9 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TitledPane.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ToggleButton.png b/src/fr/inria/structgraphics/ui/inspector/icons/ToggleButton.png new file mode 100644 index 0000000000000000000000000000000000000000..b91432d492b3fbb229e1743954cbfc565efd0e57 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ToggleButton.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/ToolBar.png b/src/fr/inria/structgraphics/ui/inspector/icons/ToolBar.png new file mode 100644 index 0000000000000000000000000000000000000000..99f521e9af6d219b5def8a81a46ab64fb3c5ce55 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/ToolBar.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Tooltip.png b/src/fr/inria/structgraphics/ui/inspector/icons/Tooltip.png new file mode 100644 index 0000000000000000000000000000000000000000..1846d4263ab4e5ba0bb5acc7a061b616e037e512 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Tooltip.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TopBottom.png b/src/fr/inria/structgraphics/ui/inspector/icons/TopBottom.png new file mode 100644 index 0000000000000000000000000000000000000000..72ca88f0b7e54c8a58ded206f9c6bad9363f88c4 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TopBottom.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/TreeView.png b/src/fr/inria/structgraphics/ui/inspector/icons/TreeView.png new file mode 100644 index 0000000000000000000000000000000000000000..ce599242af40a9a98dcab1cdfe1249685237112f Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/TreeView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/Triangle.png b/src/fr/inria/structgraphics/ui/inspector/icons/Triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..5f491c5b3e66af9fdd85b4fd77ac8c28915d6c01 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/Triangle.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/VBox.png b/src/fr/inria/structgraphics/ui/inspector/icons/VBox.png new file mode 100644 index 0000000000000000000000000000000000000000..fa33845554dc271c9c7325562d42fda41e92c0fa Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/VBox.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/WebView.png b/src/fr/inria/structgraphics/ui/inspector/icons/WebView.png new file mode 100644 index 0000000000000000000000000000000000000000..57ffb2a0f6eadc5f389ddf73fa1e54f68f93d425 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/WebView.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/bezier.png b/src/fr/inria/structgraphics/ui/inspector/icons/bezier.png new file mode 100644 index 0000000000000000000000000000000000000000..3d41147b0468f8633ef3afcedfcc6820a485040d Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/bezier.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/bezier_.png b/src/fr/inria/structgraphics/ui/inspector/icons/bezier_.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b4bf91a33e06365bf9303e8bb5d903a442d281 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/bezier_.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/beziersolid.png b/src/fr/inria/structgraphics/ui/inspector/icons/beziersolid.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b9b3f176482e0825a5fa31b149f33908a64936 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/beziersolid.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/cleaning.png b/src/fr/inria/structgraphics/ui/inspector/icons/cleaning.png new file mode 100644 index 0000000000000000000000000000000000000000..0baef1e73c80f9f0d7a5f40beb8acba8f08d07b4 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/cleaning.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/construct-2.png b/src/fr/inria/structgraphics/ui/inspector/icons/construct-2.png new file mode 100644 index 0000000000000000000000000000000000000000..65dbe5375d0ba76bb0f70538298fcf58e796350b Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/construct-2.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/construct.png b/src/fr/inria/structgraphics/ui/inspector/icons/construct.png new file mode 100644 index 0000000000000000000000000000000000000000..a1c78a5081846a579722e9e86fa44ea5e35d51e4 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/construct.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/drag.png b/src/fr/inria/structgraphics/ui/inspector/icons/drag.png new file mode 100644 index 0000000000000000000000000000000000000000..1cee3a97c709958ee726af8fc96943602aac86cd Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/drag.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/editclear.png b/src/fr/inria/structgraphics/ui/inspector/icons/editclear.png new file mode 100644 index 0000000000000000000000000000000000000000..c09720f04ec6a8cadbb23bf682b72e0a792479e8 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/editclear.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/eraser.png b/src/fr/inria/structgraphics/ui/inspector/icons/eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..572a0be29068690718e4a15219b8217118588394 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/eraser.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/flow.png b/src/fr/inria/structgraphics/ui/inspector/icons/flow.png new file mode 100644 index 0000000000000000000000000000000000000000..816e0581fdecc5c83b978c2c75a8a203135fc243 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/flow.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/library-2.png b/src/fr/inria/structgraphics/ui/inspector/icons/library-2.png new file mode 100644 index 0000000000000000000000000000000000000000..5610cd2ec3cc8bcf2232b852a1bd6086a7ebbcaa Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/library-2.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/library.png b/src/fr/inria/structgraphics/ui/inspector/icons/library.png new file mode 100644 index 0000000000000000000000000000000000000000..30e8c851d6ce95c2389f52b23979383a2e4aacd7 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/library.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/link.png b/src/fr/inria/structgraphics/ui/inspector/icons/link.png new file mode 100644 index 0000000000000000000000000000000000000000..1c5dcbb2975b9af4d7a59b4285f6cc893312418f Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/link.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/linked.png b/src/fr/inria/structgraphics/ui/inspector/icons/linked.png new file mode 100644 index 0000000000000000000000000000000000000000..3e28ac967de8ea1ae98fc5d690656e83bc6706ce Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/linked.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/lock.png b/src/fr/inria/structgraphics/ui/inspector/icons/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..fd218b1bb773cf0bbaa5f5837bdad66c42b00d88 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/lock.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/mu.png b/src/fr/inria/structgraphics/ui/inspector/icons/mu.png new file mode 100644 index 0000000000000000000000000000000000000000..ee4fe25303e704a9446291c878b41731dbda1c32 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/mu.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/multiply.png b/src/fr/inria/structgraphics/ui/inspector/icons/multiply.png new file mode 100644 index 0000000000000000000000000000000000000000..c2bf0cbeabc7bc2e0db5c0f55d1b39c0de2abee6 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/multiply.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/open-file.png b/src/fr/inria/structgraphics/ui/inspector/icons/open-file.png new file mode 100644 index 0000000000000000000000000000000000000000..0b75dd06f2ae593b4e2f16979000bc487aca66dd Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/open-file.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/open-folder.png b/src/fr/inria/structgraphics/ui/inspector/icons/open-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..a6ab2f5c9c7439957e25155c946cca5358c44cde Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/open-folder.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/save.png b/src/fr/inria/structgraphics/ui/inspector/icons/save.png new file mode 100644 index 0000000000000000000000000000000000000000..68dda5a9f099c288bc010b71327e49784609ca2c Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/save.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/select.png b/src/fr/inria/structgraphics/ui/inspector/icons/select.png new file mode 100644 index 0000000000000000000000000000000000000000..fc378c8c9ae8df3cdb52ec27a56b04dc70e3d4d0 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/select.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/straightsolid.png b/src/fr/inria/structgraphics/ui/inspector/icons/straightsolid.png new file mode 100644 index 0000000000000000000000000000000000000000..13fce1fb9b83a60fccb5fc63102179b0075796c9 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/straightsolid.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/unlinked.png b/src/fr/inria/structgraphics/ui/inspector/icons/unlinked.png new file mode 100644 index 0000000000000000000000000000000000000000..fe3ed01559bc0109dd703fd38b7a7ceb581294f2 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/unlinked.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/unlock.png b/src/fr/inria/structgraphics/ui/inspector/icons/unlock.png new file mode 100644 index 0000000000000000000000000000000000000000..7985379f7b795ba1233fc1b19c81a0ba084bd1da Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/unlock.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/x-center.png b/src/fr/inria/structgraphics/ui/inspector/icons/x-center.png new file mode 100644 index 0000000000000000000000000000000000000000..910fc9158b179f907c5107ebfd3726f7ef1c4c50 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/x-center.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/x-free.png b/src/fr/inria/structgraphics/ui/inspector/icons/x-free.png new file mode 100644 index 0000000000000000000000000000000000000000..639caf97b62a10987d7d78601745e559f12b95f7 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/x-free.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/x-left.png b/src/fr/inria/structgraphics/ui/inspector/icons/x-left.png new file mode 100644 index 0000000000000000000000000000000000000000..99077ca7ad5abc562d27424f7963a1d5969ccdd0 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/x-left.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/x-right.png b/src/fr/inria/structgraphics/ui/inspector/icons/x-right.png new file mode 100644 index 0000000000000000000000000000000000000000..9a90b48f80bb6c9abb2e1730f8932f05e54de742 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/x-right.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/y-bottom.png b/src/fr/inria/structgraphics/ui/inspector/icons/y-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..92e1d84caf8c2f056b3a61ea731079b7e7763834 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/y-bottom.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/y-center.png b/src/fr/inria/structgraphics/ui/inspector/icons/y-center.png new file mode 100644 index 0000000000000000000000000000000000000000..0f69770e094b45987086f76d56eb4204a9521ce1 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/y-center.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/y-free.png b/src/fr/inria/structgraphics/ui/inspector/icons/y-free.png new file mode 100644 index 0000000000000000000000000000000000000000..627f4cb0019b81e908dbdb8bbaa1721dba05e21d Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/y-free.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/icons/y-top.png b/src/fr/inria/structgraphics/ui/inspector/icons/y-top.png new file mode 100644 index 0000000000000000000000000000000000000000..8d97bde1f51581f3e0565b62f7fe9f36beaeb656 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/inspector/icons/y-top.png differ diff --git a/src/fr/inria/structgraphics/ui/inspector/inspector.css b/src/fr/inria/structgraphics/ui/inspector/inspector.css new file mode 100644 index 0000000000000000000000000000000000000000..b57f8061c8698ec5254d27ea77b920d2dd62a80c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/inspector.css @@ -0,0 +1,169 @@ +/* + * Scenic View, + * Copyright (C) 2012 Jonathan Giles, Ander Ruiz, Amy Fowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +.split-pane { + -fx-background-insets: 0, 1 0 1 0; + -fx-padding: 0 0 1 0; +} +.split-pane *.split-pane-divider{ + -fx-padding: 0 1 0 0; + -fx-background-color: transparent, -fx-box-border; + -fx-background-insets: 0 -3 0 -3, 0; + -fx-border-color: null; +} +.split-pane *.horizontal-grabber { + -fx-padding: 0; + -fx-shape: ""; +} + +.tree-view { + -fx-background-insets: 0, 0 0 -1 0; + -fx-background-radius: 0; + -fx-padding: 0; +} + +.tree-view:focused .tree-cell:filled:selected { + -fx-background-color: #D2B48C; // Focused color + +} + +.scanning-label { + -fx-font-size:12; + -fx-font-weight: bold; +} + +.scroll-pane { + -fx-padding: 0; + -fx-background-insets: 0; +} + +.detail-pane #title-label { + -fx-font: bold 12pt System; +} + +.detail-grid { + -fx-background-insets: 0,1,2; + -fx-background-radius: 0,0,0; + -fx-padding: 4 6 6 6; + -fx-hgap: 4; + -fx-vgap: 1; +} + +.titled-pane > .title { + -fx-background-radius: 0, 0, 0; + +} +.titled-pane > *.content { + -fx-background-insets: 0, 0 0 0 0; + -fx-padding: 0 0 -1 0; +} + +.detail-pane .detail-dummy { + -fx-font-size: 4; +} + +.detail-pane .detail-label { + -fx-font-weight: bold; + -fx-text-fill: #0082dd; +} + +.detail-pane .updated-detail-label { + -fx-font-weight: bold; + -fx-text-fill: #FF0000; +} + +.detail-pane .detail-value { + -fx-text-fill: black; + -fx-font-weight: normal; + -fx-font-size: 11; +} + +.detail-pane { + -fx-text-fill: black; +} + +.titled-table-pane { + -fx-padding: 10px 0px 0px 0px ; +} + +.nested-detail-pane { + -fx-text-fill: #0082dd; + -fx-padding: 6px ; +} + +.detail-field { + -fx-text-fill: black; + -fx-font-size: 12; +} + +.constraints-display { + -fx-padding: 2 2 2 2; +} + +.constraints-display .label.key { + -fx-text-fill: #0082dd; +} + +.gridpane-constraint-display { + -fx-padding: 2 2 2 2; + -fx-background-color: #eeeeee; +} + +.gridpane-constraint-display .label { + -fx-text-fill: #0082dd; +} + + +.search-bar .label { + -fx-font-weight: bold; +} + +#main-statusbar { + -fx-padding: 3 4 3 4; + -fx-spacing: 4; +} +#main-statusbar .value-name { + -fx-font-weight: bold; +} + +.animations-table-view .table-cell { + -fx-alignment: BASELINE_RIGHT; + } + + +.tab-pane > .tab-header-area > .tab-header-background { + -fx-effect: null; +} +/* + * For Testing + */ + +.color { + -fx-fill: orange; +} +#rect4 { + -fx-arc-width: 10; + -fx-arc-height: 10; +} +#First { + -fx-font: bold 12pt System; + -fx-background-color: red; +} + + diff --git a/src/fr/inria/structgraphics/ui/inspector/util/PropertyCloner.java b/src/fr/inria/structgraphics/ui/inspector/util/PropertyCloner.java new file mode 100644 index 0000000000000000000000000000000000000000..8e1e0557e52a9d2314d9667d810d8b2722db4e6e --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/util/PropertyCloner.java @@ -0,0 +1,90 @@ +package fr.inria.structgraphics.ui.inspector.util; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.AlignmentXProperty; +import fr.inria.structgraphics.types.AlignmentYProperty; +import fr.inria.structgraphics.types.ColorProperty; +import fr.inria.structgraphics.types.ColoringSchemeProperty; +import fr.inria.structgraphics.types.ComponentRefXProperty; +import fr.inria.structgraphics.types.ComponentRefYProperty; +import fr.inria.structgraphics.types.ContainerRefXProperty; +import fr.inria.structgraphics.types.ContainerRefYProperty; +import fr.inria.structgraphics.types.DeltaXProperty; +import fr.inria.structgraphics.types.DeltaYProperty; +import fr.inria.structgraphics.types.DistributionProperty; +import fr.inria.structgraphics.types.DistributionXProperty; +import fr.inria.structgraphics.types.DistributionYProperty; +import fr.inria.structgraphics.types.DoubleListProperty; +import fr.inria.structgraphics.types.FillColorProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.FontSizeProperty; +import fr.inria.structgraphics.types.HeightProperty; +import fr.inria.structgraphics.types.LineTypeProperty; +import fr.inria.structgraphics.types.OpacityProperty; +import fr.inria.structgraphics.types.RotationProperty; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.types.StrokeColorProperty; +import fr.inria.structgraphics.types.StrokeWidthProperty; +import fr.inria.structgraphics.types.TextProperty; +import fr.inria.structgraphics.types.WidthProperty; +import fr.inria.structgraphics.types.XProperty; +import fr.inria.structgraphics.types.YProperty; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.types.ColoringSchemeProperty.Scheme; +import javafx.beans.property.Property; +import javafx.scene.paint.Color; + +public class PropertyCloner { + + public static Property replicate(Property property) { + + Property prop; + + if(property instanceof WidthProperty) prop = new WidthProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof HeightProperty) prop = new HeightProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof XProperty) prop = new XProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof YProperty) prop = new YProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof DeltaXProperty) prop = new DeltaXProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof DeltaYProperty) prop = new DeltaYProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof RotationProperty) prop = new RotationProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof StrokeColorProperty) prop = new StrokeColorProperty(property.getBean(), (Color) property.getValue()); + else if(property instanceof FillColorProperty) prop = new FillColorProperty(property.getBean(), (Color) property.getValue()); + else if(property instanceof ContainerRefXProperty) prop = new ContainerRefXProperty(property.getBean(), (RefX) property.getValue()); + else if(property instanceof ContainerRefYProperty) prop = new ContainerRefYProperty(property.getBean(), (RefY) property.getValue()); + else if(property instanceof ComponentRefXProperty) prop = new ComponentRefXProperty(property.getBean(), (RefX) property.getValue()); + else if(property instanceof ComponentRefYProperty) prop = new ComponentRefYProperty(property.getBean(), (RefY) property.getValue()); + else if(property instanceof StrokeWidthProperty) prop = new StrokeWidthProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof AlignmentXProperty) prop = new AlignmentXProperty(property.getBean(), (YSticky) property.getValue()); + else if(property instanceof AlignmentYProperty) prop = new AlignmentYProperty(property.getBean(), (XSticky) property.getValue()); + else if(property instanceof ShapeProperty) prop = new ShapeProperty(property.getBean(), (ShapeProperty.Type) property.getValue()); + else if(property instanceof LineTypeProperty) prop = new LineTypeProperty(property.getBean(), (LineTypeProperty.Type) property.getValue()); + else if(property instanceof DistributionXProperty) prop = new DistributionXProperty(property.getBean(), (DistributionProperty.Constraint) property.getValue()); + else if(property instanceof DistributionYProperty) prop = new DistributionYProperty(property.getBean(), (DistributionProperty.Constraint) property.getValue()); + else if(property instanceof TextProperty) prop = new TextProperty(property.getBean(), (String)property.getValue()); + else if(property instanceof FontSizeProperty) prop = new FontSizeProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof OpacityProperty) prop = new OpacityProperty(property.getBean(), (double)property.getValue()); + else if(property instanceof ColoringSchemeProperty) prop = new ColoringSchemeProperty(property.getBean(), (Scheme)property.getValue()); + else if(property instanceof FlexibleListProperty) { + List<Property> list = new ArrayList<>(); + for(Property prop_: (FlexibleListProperty)property) + list.add(PropertyCloner.replicate(prop_)); + + return FlexibleListProperty.createList(property.getBean(), list); + } + else return property; + + if(property instanceof Shareable) { + ((Shareable)prop).getActiveProperty().bind(((Shareable) property).getActiveProperty()); + ((Shareable)prop).getPublicProperty().bind(((Shareable) property).getPublicProperty()); + ((Shareable)prop).getHiddenProperty().bind(((Shareable) property).getHiddenProperty()); + } + + return prop; + } +} diff --git a/src/fr/inria/structgraphics/ui/inspector/util/TooltipCreator.java b/src/fr/inria/structgraphics/ui/inspector/util/TooltipCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..b6c0223d0b3376652bea45d686e6184bac76399a --- /dev/null +++ b/src/fr/inria/structgraphics/ui/inspector/util/TooltipCreator.java @@ -0,0 +1,41 @@ +package fr.inria.structgraphics.ui.inspector.util; + +import java.lang.reflect.Field; + +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.scene.Node; +import javafx.scene.control.Tooltip; +import javafx.util.Duration; + +public class TooltipCreator { + + private final static Duration duration = new Duration(250); + + public static Tooltip createTooltip(Node node, String text) { + Tooltip t = new Tooltip(text); + Tooltip.install(node, t); + + return t; + } + + // See: https://stackoverflow.com/questions/26854301/how-to-control-the-javafx-tooltips-delay + public static void hackTooltipStartTiming() { + Tooltip tooltip = new Tooltip(); + + try { + Field fieldBehavior = tooltip.getClass().getDeclaredField("BEHAVIOR"); + fieldBehavior.setAccessible(true); + Object objBehavior = fieldBehavior.get(tooltip); + + Field fieldTimer = objBehavior.getClass().getDeclaredField("activationTimer"); + fieldTimer.setAccessible(true); + Timeline objTimer = (Timeline) fieldTimer.get(objBehavior); + + objTimer.getKeyFrames().clear(); + objTimer.getKeyFrames().add(new KeyFrame(duration)); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/fr/inria/structgraphics/ui/library/LibraryPane.java b/src/fr/inria/structgraphics/ui/library/LibraryPane.java new file mode 100644 index 0000000000000000000000000000000000000000..ddb8a79575c1dc9883b985398756510620f742ae --- /dev/null +++ b/src/fr/inria/structgraphics/ui/library/LibraryPane.java @@ -0,0 +1,238 @@ +package fr.inria.structgraphics.ui.library; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.MarkFactory; +import fr.inria.structgraphics.graphics.SimpleMark; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.persistence.JsonDescription; +import fr.inria.structgraphics.persistence.JsonObjectReader; +import fr.inria.structgraphics.persistence.JsonObjectWriter; +import fr.inria.structgraphics.ui.DisplayPreferences; +import fr.inria.structgraphics.ui.viscanvas.Canvas; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.Group; +import javafx.scene.SnapshotParameters; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.image.ImageView; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.transform.Transform; +import javafx.stage.WindowEvent; + +public class LibraryPane extends BorderPane { + + public final String DRAG_CODE= "LiBRArY"; + + private ArrayList<Mark> libraryComponents = new ArrayList<>(); + + private VisFrame fakeframe; + + private CanvasFrame canvasFrame; + + private VBox items; + private Canvas canvas; + + public LibraryPane(CanvasFrame canvasFrame) { + items = new VBox(0); + + this.canvasFrame = canvasFrame; + + canvas = new Canvas(); + canvas.getChildren().add(items); + + canvas.setStyle("-fx-background-color: #FFFFFF;"); + items.setStyle("-fx-background-color: #FFFFFF;"); + + fakeframe = MarkFactory.createVisFrame(DisplayPreferences.CANVAS_WIDTH, DisplayPreferences.CANVAS_HEIGHT); + + canvasFrame.getCanvas().setOnDragOver(new EventHandler<DragEvent>() { + public void handle(DragEvent event) { + Dragboard db = event.getDragboard(); + + //TODO: I need to fix that so that it only activate when + SimpleMark mark = canvasFrame.getDragged(); + if (mark != null && db.hasString() && db.getString().equals(DRAG_CODE)) { + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } + event.consume(); + } + }); + + canvasFrame.getCanvas().setOnDragDropped(new EventHandler <DragEvent>() { + public void handle(DragEvent event) { + Dragboard db = event.getDragboard(); + boolean success = false; + if (db.hasString() && db.getString().equals(DRAG_CODE)) { + addMarkToCanvas(canvasFrame.getDragged(), event.getX(), event.getY()); + + success = true; + } + event.setDropCompleted(success); + + event.consume(); + } + }); + + //load(); + } + + public ArrayList<Mark> getComponents() { + return libraryComponents; + } + + private void addMarkToCanvas(SimpleMark mark, double x, double y) { + SimpleMark copy = mark.createCopy(canvasFrame.getVisFrame(), 0, 0); + copy.initProperties(); + copy.getCoords().x.set(x); + copy.getCoords().y.set(copy.getRootHeight() - y); + copy.update(); + + canvasFrame.getInspector().update(); + } + + public Canvas getCanvas() { + return canvas; + } + + public void addToLibrary(SimpleMark mark) { + SimpleMark copy = mark.createCopy(fakeframe, 0, 0); + copy.initProperties(); + copy.update(); + libraryComponents.add(mark); + addToList(copy); + + save(); + } + + private void addToList(SimpleMark mark) { + Group group = mark.getGroup(); + + //double w = group.getBoundsInLocal().getWidth(); + double scalex = Math.min(1, ( CanvasFrame.LIB_WIDTH - 60) / mark.width()); + double scaley = Math.min(1, 100/mark.height()); + double scale = Math.min(scalex, scaley); + + SnapshotParameters params = new SnapshotParameters(); + params.setTransform(Transform.scale(scale, scale)); + + ImageView imageView = new ImageView(); + imageView.setImage(group.snapshot(params, null)); + + HBox hbox = new HBox(0); + hbox.setAlignment(Pos.CENTER); + //hbox.setPadding(new Insets(10, 10, 10, 10)); + hbox.setStyle("-fx-padding: 8;" + "-fx-border-style: solid inside;" + + "-fx-border-width: 2;" + "-fx-border-insets: 5;" + + "-fx-border-radius: 5;" + "-fx-border-color: #aaaadd;"); + + hbox.getChildren().add(imageView); + + items.getChildren().add(hbox); + + hbox.setOnDragDetected( + new EventHandler<MouseEvent>() { + public void handle(MouseEvent event) { + Dragboard db = imageView.startDragAndDrop(TransferMode.COPY); + ClipboardContent content = new ClipboardContent(); + content.putString(DRAG_CODE); + db.setContent(content); + + canvasFrame.setDraggable(mark); + event.consume(); + } + }); + + hbox.setOnMousePressed(new EventHandler<MouseEvent>() { + + @Override + public void handle(MouseEvent event) { + + if(event.isSecondaryButtonDown()) { + ContextMenu contextMenu = new ContextMenu(); + + MenuItem item = new MenuItem("Remove from Library"); + item.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + libraryComponents.remove(items.getChildren().indexOf(hbox)); + items.getChildren().remove(hbox); + save(); + } + }); + + contextMenu.getItems().add(item); + + contextMenu.setOnHiding(new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent event) { + canvasFrame.getSplitPane().setContextMenu(null); + } + }); + + canvasFrame.getSplitPane().setContextMenu(contextMenu); + } + } + }); + } + + public void clear() { + items.getChildren().clear(); + } + + public void load() { + File libfile = new File("./sessions/library.json"); + if(libfile == null) return; + + JsonArray visArray; + JsonDescription descr = new JsonDescription(libfile); + visArray = descr.isLibrary() ? descr.getLibraryArray(false) : descr.getVisArray(false); + load(visArray); + } + + public void load(JsonArray visArray) { + if(visArray != null && !visArray.isEmpty()) + libraryComponents = JsonObjectReader.readChildrenFromJasonArray(fakeframe, visArray); + + for(Mark mark:libraryComponents) { + addToList((SimpleMark)mark); + } + } + + public void save() { + if(libraryComponents.isEmpty()) return; + + File libfile = new File("./sessions/library.json"); + if(libfile == null) return; + else { + JsonObject visualizations = + JsonObjectWriter.saveToJason(libraryComponents, true); + try { + FileWriter writer = new FileWriter(libfile, false); + writer.write(JsonObjectWriter.fromObjectToReadableString(visualizations, 0).toString()); + writer.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/AreaContent.java b/src/fr/inria/structgraphics/ui/spreadsheet/AreaContent.java new file mode 100644 index 0000000000000000000000000000000000000000..b979e31a360883f0e6fd19d3b6c3d064b0b75143 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/AreaContent.java @@ -0,0 +1,47 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +public class AreaContent { + + private Map<String, DataVariable> variables = new TreeMap<>(); + + public AreaContent() {} + + public AreaContent(String name, DataVariable variable) { + variables.put(name, variable); + } + + public void addVariable(String name, DataVariable variable) { + variables.put(name, variable); + } + + public DataVariable getVariable(String name) { + return variables.get(name); + } + + public Set<String> getVariableNames(){ + return variables.keySet(); + } + + public Collection<DataVariable> getVariables(){ + return variables.values(); + } + + public DataVariable getVariable(int row, int column) { + for(DataVariable var: variables.values()) + if(var.contains(row, column)) return var; + //if(var.inHeader(row, column)) return var; + + return null; + } + + public int getVIndex(DataVariable variable, int column) { + if(variable == null) return 0; + else return variable.getColumnIndex(column); + } + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/AreaInteractor.java b/src/fr/inria/structgraphics/ui/spreadsheet/AreaInteractor.java new file mode 100644 index 0000000000000000000000000000000000000000..72c4ac41f278f25dec9add542c9c7acf61f00ef2 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/AreaInteractor.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.ui.tools.SelectTool; +import fr.inria.structgraphics.ui.tools.Tool; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import javafx.beans.property.Property; +import javafx.collections.ObservableList; +import javafx.scene.control.TablePosition; +import sun.security.provider.certpath.CollectionCertStore; + +public class AreaInteractor { + + private List<SpreadsheetArea> areas = new ArrayList<>(); + private DataVariable activeVariable = null; + private TransformationField transformField; + + private SelectTool selectTool; + + public AreaInteractor(TransformationField transformField){ + this.transformField = transformField; + } + + public List<SpreadsheetArea> getAreas(){ + return areas; + } + + private LineConnectedCollection selectedCollection; + + // It controls the interaction with the spreadsheet... + public void clickedOn(ObservableList<TablePosition> selectedCells) { //System.err.println(x); + activeVariable = null; + + if(selectedCells.size() == 1) { + TablePosition pos = selectedCells.get(0); + + for(SpreadsheetArea area: areas) { + activeVariable = area.getVariable(pos.getRow(), pos.getColumn()); + if(activeVariable != null) { + int vindex = area.getVIndex(activeVariable, pos.getColumn()); + transformField.disable(false); + transformField.setVariable(activeVariable, vindex); // TODO: ????? + + int rowindex = pos.getRow() - activeVariable.row - 1; + if(rowindex >= 0) { + Property property = activeVariable.getProperty(rowindex, vindex); + if(property == null) { + selectTool.select(null, this); + break; + } + Object obj = property.getBean(); + + if(obj instanceof Mark) { + if(selectedCollection != null) { + selectedCollection.selectConnection(null); + selectedCollection = null; + } + selectTool.setActive(true); + selectTool.select((Mark)obj, this); + } else if(obj instanceof FlowConnection) { + LineConnectedCollection collection = ((FlowConnection)obj).getCollection(); + if(selectedCollection != null && selectedCollection != collection) { + selectedCollection.selectConnection(null); + } + + collection.selectConnection((FlowConnection)obj); + selectedCollection = collection; + } + } else { + if(selectedCollection != null) { + selectedCollection.selectConnection(null); + selectedCollection = null; + } + selectTool.select(null, this); + } + + return; + } + } + } + + if(selectedCollection != null) { + selectedCollection.selectConnection(null); + selectedCollection = null; + } + selectTool.select(null, this); + transformField.disable(true); + transformField.setDefaultLabel(); + } + + public void addArea(SpreadsheetArea area) { + areas.add(area); + } + + public void update() { + for(int i = areas.size() - 1; i >=0; --i) { + SpreadsheetArea area = areas.get(i); + area.clear(); + if(!area.refresh()) areas.remove(i); + } + } + + public void reset() { + for(SpreadsheetArea area: areas) { + area.clearSketcherLabels(); + area.clear(); + } + + areas.clear(); + } + + public DataVariable getActiveVariable() { + return activeVariable; + } + + public void setSelector(SelectTool select) { + this.selectTool = select; + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/AreaSourceInfo.java b/src/fr/inria/structgraphics/ui/spreadsheet/AreaSourceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..96850dc02b6b9940f089d93c88a7a5cfd6983a3f --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/AreaSourceInfo.java @@ -0,0 +1,114 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.ArrayList; +import java.util.Collection; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.Draggable.Type; +import javafx.beans.property.Property; + +public class AreaSourceInfo { + + protected Container source; + protected VisBody visbody = null; + protected Type type; + protected boolean wide = false; + protected boolean isNetwork = false; + protected ArrayList<PropertyName> names = new ArrayList<>(); + + protected Property property = null; // Only used for single self properties. + + public AreaSourceInfo(Draggable draggable) { + + source = draggable.getContainer(); + visbody = draggable.getGroup(); + type = draggable.getType(); + wide = draggable.isInWide(); + isNetwork = draggable.isNetwork(); + + //if(source == null || source.getChildPropertyStructure().getProperties().containsKey(key)) + + Object content = draggable.getPropertiesContent(); + if(content instanceof FlexibleListProperty) { + PropertyName propName = ((FlexibleListProperty) content).getPropertyName(); + names.add(propName); + + if(type == Type.Value) { + property = ((FlexibleListProperty) content).get(0); + } + + } else { + for(FlexibleListProperty list: (Collection<FlexibleListProperty>)content) { + names.add(list.getPropertyName()); + } + } + } + + public AreaSourceInfo(Container container, VisBody visbody, ArrayList<PropertyName> names, Type type, boolean isWide, boolean isNetwork) { + this.source = container; + this.visbody = visbody; + this.type = type; + this.wide = isWide; + this.isNetwork = isNetwork; + this.names = names; + + if(type == Type.Value && !names.isEmpty()) { + property = ((Mark)container).getProperty(names.get(0)); + } + } + + public Container getSource() { + return source; + } + + public VisBody getVisBody() { + return source.getVirtualGroup(); + } + + public Type getType() { + return type; + } + + public boolean isWide() { + return wide; + } + + public boolean showsNetwork() { + return isNetwork; + } + + public Property getProperty() { + return property; + } + + public ArrayList<PropertyName> getNames(){ + return names; + } + + public boolean sourceExists() { + return (source != null) && (source instanceof VisFrame || source instanceof Mark && ((Mark)source).getRoot() != null); + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + + if(property!= null) buffer.append("? "); + else if(wide) buffer.append("> "); + + for(PropertyName name: names) { + buffer.append(name.toString()); + buffer.append(" "); + } + + buffer.append("\n"); + + return buffer.toString(); + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/ColumnVariable.java b/src/fr/inria/structgraphics/ui/spreadsheet/ColumnVariable.java new file mode 100644 index 0000000000000000000000000000000000000000..52f9dcb24ac57ad3e9dc1e208339661db1af7469 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/ColumnVariable.java @@ -0,0 +1,57 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import javafx.beans.property.ListProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class ColumnVariable extends DataVariable { + + private ListProperty<Property> properties; + protected StringProperty name = new SimpleStringProperty(); + + public ColumnVariable(VisBody collection, int row, int column, ListProperty<Property> properties) { + super(collection, row, column); + name.set(properties.getName()); + this.properties = properties; + + setTransformation(); + } + + public void updateProperties(ListProperty<Property> properties) { + this.properties = properties; + } + + @Override + public StringProperty getName(int vindex) { + return name; + } + + @Override + public String getPropertyName() { + return properties.getName(); + } + + @Override + public int getLength() { + return properties.size(); + } + + @Override + public int getWidth() { + return 1; + } + + @Override + public Property getProperty(int vindex, int hindex) { + return properties.get(vindex); + } + + @Override + public boolean contains(int row, int column) { + return this.column == column && row >= this.row && row <= this.row + properties.size(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/ComparableDataValue.java b/src/fr/inria/structgraphics/ui/spreadsheet/ComparableDataValue.java new file mode 100644 index 0000000000000000000000000000000000000000..a5423b18b81fab3edff04fe49922702ea41a7a03 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/ComparableDataValue.java @@ -0,0 +1,38 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import javafx.beans.property.Property; + +public class ComparableDataValue implements Comparable<ComparableDataValue> { + + private Property value; + + public ComparableDataValue(Property value) { + this.value = value; + } + + public Object getValue() { + return value.getValue(); + } + + public Property getProperty() { + return value; + } + + @Override + public int compareTo(ComparableDataValue obj) { + if(value.getValue() instanceof Double && obj.value.getValue() instanceof Double) + return ((Double)value.getValue()).compareTo((Double)obj.value.getValue()); + else return value.getValue().toString().compareTo(obj.value.getValue().toString()); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof ComparableDataValue) return value.getValue().equals(((ComparableDataValue)obj).value.getValue()); + else return false; + } + + @Override + public String toString() { + return value.getValue().toString(); + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/DataTransformation.java b/src/fr/inria/structgraphics/ui/spreadsheet/DataTransformation.java new file mode 100644 index 0000000000000000000000000000000000000000..15e0e583eca35b23dc0907277761edcaa36a8479 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/DataTransformation.java @@ -0,0 +1,31 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.StringProperty; + +public abstract class DataTransformation { + protected DataVariable variable; + protected StringProperty expression; + protected BooleanProperty change = new SimpleBooleanProperty(true); + + public DataTransformation(DataVariable variable) { + this.variable = variable; + } + +// public abstract Object fromData(Property source, Object oldValue, Object newValue); + public abstract Object fromData(Property source, Object newValue); + public abstract String toData(Object val); + public abstract boolean setExpression(String expr); + + public BooleanProperty changeProperty() { + return change; + } + + public StringProperty getExpression() { + return expression; + } + + public void refresh() {} +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/DataTransformationProperty.java b/src/fr/inria/structgraphics/ui/spreadsheet/DataTransformationProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..68fdcec10d664649bd6b1afb727027caea987348 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/DataTransformationProperty.java @@ -0,0 +1,29 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +public class DataTransformationProperty extends SimpleObjectProperty<DataTransformation>{ + + public DataTransformationProperty() { + super(); + } + + private ChangeListener listener = new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + fireValueChangedEvent(); + } + }; + + @Override + public void set(DataTransformation transformation) { + if(get() != null) get().changeProperty().removeListener(listener); + + super.set(transformation); + transformation.changeProperty().addListener(listener); + } + + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/DataVariable.java b/src/fr/inria/structgraphics/ui/spreadsheet/DataVariable.java new file mode 100644 index 0000000000000000000000000000000000000000..41b54a9869d7bdad47dc2638d05157cf7d6beb21 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/DataVariable.java @@ -0,0 +1,245 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; + +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.PropertyName; +import javafx.beans.InvalidationListener; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +public abstract class DataVariable { + + public enum DataType { + Symbolic, Functional + } + + public BooleanProperty scaleShownProperty = new SimpleBooleanProperty(false); + public BooleanProperty scaleShownPropertyOuter = new SimpleBooleanProperty(false); + public BooleanProperty legendShownProperty = new SimpleBooleanProperty(false); + public BooleanProperty nodeShownProperty = new SimpleBooleanProperty(false); + + protected MathTransformation mathTransformation; + protected DiscreteTransformation discreteTransformation; + + protected VisBody collection; + + protected int row, column; + protected DataType type = DataType.Functional; + protected DataTransformationProperty transformation = new DataTransformationProperty(); + + protected InvalidationListener listener; + + public DataVariable(VisBody collection, int row, int column) { + this.collection = collection; + this.row = row; + this.column = column; + + transformation.addListener(new ChangeListener<DataTransformation>() { + @Override + public void changed(ObservableValue<? extends DataTransformation> observable, DataTransformation oldTransform, + DataTransformation newTranform) { + if(newTranform instanceof DiscreteTransformation) type = DataType.Symbolic; + else type = DataType.Functional; + } + }); + } + + protected void setTransformation() { + if(type == DataType.Functional) { + if(mathTransformation == null) mathTransformation = new MathTransformation(this); + transformation.set(mathTransformation); + } + else { + discreteTransformation = new DiscreteTransformation(this); + //if(discreteTransformation == null) discreteTransformation = new DiscreteTransformation(this); + transformation.set(discreteTransformation); + } + } + + public boolean isConnectionNodeID() { + return false; + } + + public boolean isNested() { + if(collection == null) return false; + + int level = getLevel() + 1; + int colLevel = collection.getLevel(); + + return level < colLevel; + } + + public abstract String getPropertyName(); + public abstract int getLength(); + public abstract int getWidth(); + public abstract Property getProperty(int vindex, int hindex); + public abstract boolean contains(int row, int column); + public abstract StringProperty getName(int vindex); + + public ArrayList<String> getNames(){ + ArrayList<String> names = new ArrayList<>(); + for(int i = 0; i < getWidth(); ++i) { + names.add(getName(i).get()); + } + + return names; + } + + public void setInvalidationListener(InvalidationListener listener) { + if(this.listener != null) + transformationProperty().removeListener(this.listener); + + this.listener = listener; + transformationProperty().addListener(listener); + } + + public void updatePosition(int row, int column) { + this.row = row; + this.column = column; + } + + public int getWidth(int row) { + return getWidth(); + } + + public int row() { + return row; + } + public int column() { + return column; + } + + public boolean isNumeric() { + return firstProperty() instanceof DoubleProperty; + } + + public Property firstProperty() { + return getProperty(0, 0); + } + + public DataType getType() { + return type; + } + + public DataType getDataType() { + return type; + } + + public void setDataType(DataType type) { + this.type = type; + setTransformation(); + } + + public boolean inHeader(int row, int column) { + return this.column == column && this.row == row; + } + + public ObjectProperty<DataTransformation> transformationProperty() { + return transformation; + } + + public int getColumnIndex(int columnIndex) { + return 0; + } + + public VisBody getCollection() { + return collection; + } + + public List<VisBody> getLevelGroups(){ + List<VisBody> list = new ArrayList<VisBody>(); + + /* + int level = getLevel() + 1; + collection.addToGroupLevel(list, level); + */ + if(collection != null) collection.addToInnerCollections(list); + + return list; + } + + public List<VisBody> getParentGroup() { + List<VisBody> list = new ArrayList<VisBody>(); + + /* + int level = getLevel() + 2; + if(collection != null) collection.addToParentGroupLevel(list, level); + */ + if(collection != null) collection.addToOuterCollection(list); + + return list; + } + + public boolean isX() { + return getPropertyName().contains("x"); + } + + public boolean isY() { + return getPropertyName().contains("y"); + } + + public boolean isWidth() { + return getPropertyName().contains("width"); + } + + public boolean isHeight() { + return getPropertyName().contains("height"); + } + + public boolean isWeight() { + return getPropertyName().contains("weight"); + } + + + public boolean isShape() { + return getPropertyName().contains("shape"); + } + + public boolean isFill() { + return getPropertyName().contains("fill"); + } + + public boolean isStroke() { + return getPropertyName().contains("stroke"); + } + + public boolean isThickness() { + return getPropertyName().contains("thickness"); + } + + public boolean isID() { + return getPropertyName().endsWith("id"); + } + + public int getLevel() { + return new PropertyName(getPropertyName()).getLevel(); + /* + String name = getPropertyName(); + String str = name.replaceAll("[a-z]", "").replace("-", ""); + if(str.isEmpty()) return 0; + else return Integer.parseInt(str);*/ + } + + public SortedSet<ComparableDataValue> getDiscreteValues() { + SortedSet<ComparableDataValue> list = new TreeSet<>(); + + for(int i = 0; i < getWidth(); ++i) { + for(int j = 0; j < getLength(); ++j) { + list.add(new ComparableDataValue(getProperty(j, i))); + } + } + return list; + } + + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/DataView.java b/src/fr/inria/structgraphics/ui/spreadsheet/DataView.java new file mode 100644 index 0000000000000000000000000000000000000000..52d1c28e8971d11fe2b85be166b7dfb306dd9a6c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/DataView.java @@ -0,0 +1,162 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.Optional; + +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.ui.DragManager; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.inspector.InspectorView; +import fr.inria.structgraphics.ui.tools.SelectTool; +import fr.inria.structgraphics.ui.tools.Tool; +import impl.org.controlsfx.spreadsheet.CellView; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Separator; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.ImageView; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.PickResult; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +public class DataView extends BorderPane { + + public static final String STYLESHEETS = DataView.class.getResource("spreadsheet.css").toExternalForm(); + + private Spreadsheet spreadsheet; + private TransformationField transformField; + + private InspectorView inspector; + private AreaInteractor interactor; + + public DataView(InspectorView inspector) { + this.inspector = inspector; + inspector.setDataView(this); + + buildUI(); + } + + public Spreadsheet getSpreadsheet() { + return spreadsheet; + } + + private void cleanSpreadsheet() { + spreadsheet.clean(); + transformField.disable(true); + transformField.setDefaultLabel(); + } + + private void buildUI() { + Button clean = new Button("Clean", new ImageView(DisplayUtils.getIcon("cleaning"))); + clean.setOnAction( + new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent e) { + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Confirmation Dialog"); + alert.setHeaderText(null); + alert.setContentText("Are you sure you want to clean the spreadsheet?"); + + Optional<ButtonType> result = alert.showAndWait(); + if (result.get() == ButtonType.OK){ + cleanSpreadsheet(); + } else { + // ... user chose CANCEL or closed the dialog + } + } + }); + + HBox toolBar = new HBox(clean); + toolBar.getChildren().add(new Separator(Orientation.VERTICAL)); + + transformField = new TransformationField(toolBar); + StackPane stack = new StackPane(); + + interactor = new AreaInteractor(transformField); + spreadsheet = new Spreadsheet(this, interactor); + + stack.getChildren().setAll(spreadsheet); + + setTop(transformField); + setCenter(stack); + + spreadsheet.setOnDragOver(new EventHandler<DragEvent>() { + public void handle(DragEvent event) { + Dragboard db = event.getDragboard(); + + /* data is dragged over the target */ + /* accept it only if it is not dragged from the same node + * and if it has a string data */ + if (event.getGestureSource() != spreadsheet && db.hasString()) { + /* allow for both copying and moving, whatever user chooses */ + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + + PickResult pick = event.getPickResult(); + if(pick != null) { + Node node = pick.getIntersectedNode(); + + if(node instanceof CellView && db.getString().equals(DragManager.DRAG_CODE)) { + int columns = inspector.getDragged().getColumnsNum(); + int rows = inspector.getDragged().getRowsNum(); + + CellView cellView = (CellView)node; + spreadsheet.dragOver(cellView, rows, columns); + } + } + + // spreadsheetView.addEventHandler(MouseEvent.MOUSE_MOVED, eventHandler); + } + + event.consume(); + } + }); + + spreadsheet.setOnDragDropped(new EventHandler <DragEvent>() { + public void handle(DragEvent event) { + /* data dropped */ + /* if there is a string data on dragboard, read it and use it */ + Dragboard db = event.getDragboard(); + boolean success = false; + if (db.hasString() && db.getString().equals(DragManager.DRAG_CODE)) { + spreadsheet.dragEnded(inspector.getDragged()); + success = true; + } + /* let the source know whether the string was successfully + * transferred and used */ + event.setDropCompleted(success); + + event.consume(); + } + }); + } + + public VisFrame getVisFrame() { + return inspector.getVisualizationFrame(); + } + + public void reset() { + spreadsheet.reset(); + transformField.disable(true); + transformField.setDefaultLabel(); + } + + public void setSelector(SelectTool select) { + interactor.setSelector(select); + } + + public void update() { + spreadsheet.update(); + } +} + + diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/DiscreteTransformation.java b/src/fr/inria/structgraphics/ui/spreadsheet/DiscreteTransformation.java new file mode 100644 index 0000000000000000000000000000000000000000..010b369b52f5452e1886635736b14de908094fa4 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/DiscreteTransformation.java @@ -0,0 +1,188 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +public class DiscreteTransformation extends DataTransformation { + + private final static String[] DEFAULT_CATEGORIES = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}; + + private TreeMap<MappingProperty, StringProperty> map; + private int i = 0; + + private Hashtable<Mark, String> symbols = new Hashtable<>(); + + public DiscreteTransformation(DataVariable variable) { + super(variable); + + expression = new SimpleStringProperty(); + + PropertySet propertySet = new PropertySet(variable); + map = new TreeMap<>(); + + for(MappingProperty property: propertySet) { + MappingProperty copy = property.getCopy(); + + // TODO: This is to synchronize the x,y with the mapped symbols + // I may need to consider two different types of discrete transforms to + // support this behavior + if(variable.isX() || variable.isY()) copy.bind(property); + + SimpleStringProperty symbol = (i < DEFAULT_CATEGORIES.length) ? new SimpleStringProperty(DEFAULT_CATEGORIES[i++]) : new SimpleStringProperty("A" + (i++)); + symbols.put((Mark)property.getBean(), symbol.get()); + map.put(copy, symbol); + + property.addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) { + updateExpression(); + // TODO: Need to find a way to handle Ids!!! + } + }); + } + + updateExpression(); + change.set(!change.get()); + } + + void printMap() { + for(MappingProperty prop:map.keySet()) { + System.err.println(prop + "----> " + map.get(prop)); + } + } + + public TreeMap<MappingProperty, StringProperty> getMap(){ + return map; + } + + @Override + public void refresh() { + ///// + PropertySet propertySet = new PropertySet(variable); + + TreeMap<MappingProperty, StringProperty> map_ = new TreeMap<>(); + for(MappingProperty property: propertySet) { + MappingProperty copy = property.getCopy(); + + // TODO: This is to synchronize the x,y with the mapped symbols + // I may need to consider two different types of discrete transforms to + // support this behavior + if(variable.isX() || variable.isY()) copy.bind(property); + + if(map.containsKey(property)) map_.put(copy, map.get(property)); + else { + String symbol = symbols.get((Mark)property.getBean()); + if(symbol == null) + map_.put(copy, (i < DEFAULT_CATEGORIES.length) ? new SimpleStringProperty(DEFAULT_CATEGORIES[i++]) : new SimpleStringProperty("A" + (i++))); + else map_.put(copy, new SimpleStringProperty(symbol)); + } + } + + map.clear(); + map = map_; + + updateExpression(); + } + + + public void replace(String from, String to) { + for(MappingProperty prop: map.keySet()) { + if(prop.get().toString().equals(from)) { + map.get(prop).set(to); + symbols.put((Mark)prop.getBean(), to); + updateExpression(); + change.set(!change.get()); + } + } + } + + @Override + public Object fromData(Property source/*, Object oldValue*/, Object newValue) { + if(variable.isID()) { + for(MappingProperty prop: map.keySet()) { + if(prop.getValue().equals(source.getValue())) { + map.get(prop).set(newValue.toString()); + symbols.put((Mark)prop.getBean(), newValue.toString()); + updateExpression(); + change.set(!change.get()); + return prop.get(); + } + } + } + + + // TODO: Do it better!!! + for(MappingProperty prop: map.keySet()) { + if(map.get(prop).get().equals(newValue)) { + symbols.put((Mark)prop.getBean(), newValue.toString()); + return prop.get(); + } + } + + String oldValue = toData(source.getValue()); + for(MappingProperty prop: map.keySet()) { + StringProperty entry = map.get(prop); + if(entry.get().equals(oldValue)) { // System.err.println(prop + " >>> " + newValue); + map.get(prop).set(newValue.toString()); + symbols.put((Mark)prop.getBean(), newValue.toString()); + + // map.put(prop, new SimpleStringProperty(newValue.toString())); // TODO Update this!!!!!! + updateExpression(); + change.set(!change.get()); + return prop.get(); + } + } + + return null; + } + + + @Override + public String toData(Object val) { + StringProperty value = map.get(new MappingProperty(val)); + if(value != null) return map.get(new MappingProperty(val)).get(); + else return null; + } + + @Override + public boolean setExpression(String expr) { + change.set(!change.get()); + + return false; + } + + private void updateExpression() { + StringBuffer str = new StringBuffer(variable.getPropertyName() + ": "); + + List<MappingProperty> unmapped = new ArrayList<>(); + for(MappingProperty prop:map.keySet()) { + StringProperty value = map.get(prop); + if(value != null) + str.append("{ " + prop.get() + " -> " + value.get() + " } "); + else // TODO: .... + str.append("{ " + prop.get() + " -> " + "?" + " } "); + } + + expression.set(str.substring(0, str.length() - 1)); + + //System.err.println(expression.get()); + // TODO: There is still some buggy behavior, not sure why.... + } + + /* + public void update(Object value, String newValue) { + map.put(new MappingProperty(value), new SimpleStringProperty(newValue)); + updateExpression(); + }*/ +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/MappingProperty.java b/src/fr/inria/structgraphics/ui/spreadsheet/MappingProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..9b5864d7416aae6a171e67c1d516332ba2865103 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/MappingProperty.java @@ -0,0 +1,54 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +public class MappingProperty extends SimpleObjectProperty<Object> implements Comparable<MappingProperty> { + + public MappingProperty(Property property) { + super(property.getBean(), property.getName()); + set(property.getValue()); + this.bind(property); + } + + public MappingProperty(Object obj) { + set(obj); + } + + public MappingProperty(Object bean, String name) { + super(bean, name); + } + + @Override + public int compareTo(MappingProperty prop) { + if(equals(prop)) { + return 0; + } + else { + if(get() instanceof Double) return ((Double)get()).compareTo((Double)prop.get()); + else return get().toString().compareTo(prop.get().toString()); + } + } + + public MappingProperty getCopy() { + MappingProperty copy = new MappingProperty(getBean(), getName()); + copy.set(this.get()); + + return copy; + } + + @Override + public boolean equals(Object o) { + if(o instanceof Property) { + Object val1 = getValue(); + Object val2 = ((Property)o).getValue(); + if(val1 instanceof Double && val2 instanceof Double) { + return Math.abs(((Double)val1) - ((Double)val2)) < .001; + } + else return ((Property)o).getValue().equals(getValue()); + } + else return false; + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/MathTransformation.java b/src/fr/inria/structgraphics/ui/spreadsheet/MathTransformation.java new file mode 100644 index 0000000000000000000000000000000000000000..d99b205e5d20574b103202b0f50cf7f80b03f140 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/MathTransformation.java @@ -0,0 +1,240 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.text.DecimalFormat; + +import org.matheclipse.core.eval.ExprEvaluator; +import org.matheclipse.core.interfaces.IExpr; +import org.matheclipse.parser.client.SyntaxError; +import org.matheclipse.parser.client.math.MathException; + +import fr.inria.structgraphics.types.AlignmentXProperty; +import fr.inria.structgraphics.types.AlignmentYProperty; +import fr.inria.structgraphics.types.ColorProperty; +import fr.inria.structgraphics.types.ComponentRefXProperty; +import fr.inria.structgraphics.types.ComponentRefYProperty; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.ui.inspector.ColorStringConverter; +import fr.inria.structgraphics.ui.inspector.DoubleStringConverter; +import fr.inria.structgraphics.ui.inspector.GeneralStringConverter; +import fr.inria.structgraphics.ui.inspector.RefXStringConverter; +import fr.inria.structgraphics.ui.inspector.RefYStringConverter; +import fr.inria.structgraphics.ui.inspector.ShapeStringConverter; +import fr.inria.structgraphics.ui.inspector.XAlignmentStringConverter; +import fr.inria.structgraphics.ui.inspector.YAlignmentStringConverter; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +public class MathTransformation extends DataTransformation { + private boolean decimals = false; + + private StringProperty invexpression; + private GeneralStringConverter converter; + + private StringProperty expression0; + + private String varName; + + public MathTransformation(DataVariable variable) { + super(variable); + + expression0 = new SimpleStringProperty(variable.getPropertyName()); + varName = fixVarName(variable.getPropertyName()); + expression = new SimpleStringProperty(varName); + invexpression = new SimpleStringProperty("$Y"); + + expression0.addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + expression.set(fixVarName(newValue)); + } + }); + + expression.addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + expression0.set(newValue.replaceAll(varName, variable.getPropertyName())); + } + }); + + Property prop = variable.firstProperty(); + if(prop instanceof ColorProperty) { + converter = new ColorStringConverter(); + } else if(prop instanceof ShapeProperty) { + converter = new ShapeStringConverter(); + } else if(prop instanceof DoubleProperty) { + converter = new DoubleStringConverter(); + } else if(prop instanceof AlignmentXProperty) { + converter = new XAlignmentStringConverter(); + } else if(prop instanceof AlignmentYProperty) { + converter = new YAlignmentStringConverter(); + } else if(prop instanceof ComponentRefXProperty) { + converter = new RefXStringConverter(); + } else if(prop instanceof ComponentRefYProperty) { + converter = new RefYStringConverter(); + } + } + + @Override + public Object fromData(Property source, /*Object oldValue,*/ Object newValue) { + if(variable.firstProperty() instanceof ColorProperty) { + String stringValue = newValue.toString(); + if(stringValue == null || stringValue.isEmpty()) newValue = toData(source.getValue());//oldValue; + + /* + long val = paintToNumber(newValue.toString()); + ExprEvaluator util = new ExprEvaluator(); + String expr = "$Y=" + val + ";" + invexpression.get(); + IExpr result = util.evaluate(expr); + */ + return stringToPaint(newValue.toString()); + //return stringToPaint(newValue); + } else if(variable.firstProperty() instanceof DoubleProperty) { + double val; + + try{ + val = Double.parseDouble(newValue.toString()); + + ExprEvaluator util = new ExprEvaluator(); + String expr = "$Y=" + val + ";" + invexpression.get(); + IExpr result = util.evaluate(expr); + + return result.evalDouble(); + + } catch(NumberFormatException e) { + //val = Double.parseDouble(oldValue.toString()); + return source.getValue(); + } + + } else if(variable.firstProperty() instanceof StringProperty) { + return newValue; + } + else return converter.fromString(newValue.toString()); + } + + + public double transform(Object val) { + if(val instanceof Double){ + ExprEvaluator util = new ExprEvaluator(); + String expr = varName + "=" + val +";" + expression.get(); + return util.evaluate(expr).evalDouble(); + } else return 0; + } + + public double inverseTransform(double val) { + ExprEvaluator util = new ExprEvaluator(); + String expr = "$Y=" + val + ";" + invexpression.get(); + IExpr result = util.evaluate(expr); + + return result.evalDouble(); + } + + @Override + public String toData(Object val) { + if(val instanceof Paint) { + //ExprEvaluator util = new ExprEvaluator(); + //String expr = varName + "=" + val +";" + expression.get(); + + //return numberToPaint((long) util.evaluate(expr).evalDouble()).toString(); + return val.toString(); + } else if(val instanceof Double){ + DecimalFormat df = new DecimalFormat(decimals ? "#.0" : "#"); + + ExprEvaluator util = new ExprEvaluator(); + String expr = varName + "=" + val +";" + expression.get(); + + return df.format(util.evaluate(expr).evalDouble()); + } else if(val instanceof String || converter == null) { + return val.toString(); + } + else { + return converter.toString(val); + } + } + + private Paint stringToPaint(String string) { + Color color; + try { + color = Color.web(string); + } catch(Exception e) { + color = Color.LIGHTGRAY; + } + + return color; + } + + /* + private Long paintToString(String paintValue) { + Color color; + + try { + color = Color.web(paintValue); + } catch(Exception e) { + color = Color.LIGHTGRAY; + } + + return Long.parseLong(color.toString().substring(2), 16); + } + + private Paint stringToPaint(String string) { + try { + //return Paint.valueOf("0x" + Long.toHexString(number)); + return Paint.valueOf(string); + } catch(IllegalArgumentException e) { + return null; + } + }*/ + + @Override + public StringProperty getExpression() { + return expression0; + } + + @Override + public boolean setExpression(String expr) { + expr = fixVarName(expr); + + if(!expr.contains(varName)) return false; + + try { + ExprEvaluator util = new ExprEvaluator(); + IExpr result = util.evaluate(expr); + expr = result.toString(); + if(!expr.contains(varName)) return false; + + /* + if(expr.contains("Abs(" + varName + ")")) { + expr.replaceAll("Abs(" + varName + ")", varName); + isAbsolute = true; + }*/ + + String str = "Roots($Y==" + expr + ", " + varName +")"; + str = util.evaluate(str).toString(); + + invexpression.set(str.substring(str.indexOf("=") + 2)); + expression.set(expr); + change.set(!change.get()); + + return true; + } catch (SyntaxError e) { + return false; + } catch (MathException me) { + return false; + } catch (Exception e) { + return false; + } + } + + + // This is to artificially fix the variable name problems!!!! + private static String fixVarName(String var) { + return var.replaceAll("[A-Z].", "").replaceAll("[a-z]-[x-y]", ""); + } + + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/MultiColumnVariable.java b/src/fr/inria/structgraphics/ui/spreadsheet/MultiColumnVariable.java new file mode 100644 index 0000000000000000000000000000000000000000..c0482151e6bb73a6e655cc9bde7a64b8032b4711 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/MultiColumnVariable.java @@ -0,0 +1,95 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import javafx.beans.property.ListProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class MultiColumnVariable extends DataVariable { + + private ListProperty<Property> properties; + protected List<SimpleStringProperty> names = new ArrayList<SimpleStringProperty>(); + + private boolean isConnectionId = false; + + public MultiColumnVariable(VisBody collection, int row, int column, ListProperty<Property> properties) { + super(collection, row, column); + this.properties = properties; + + for(int i=0; i < getWidth(); ++i) + names.add(new SimpleStringProperty(properties.getName())); + + setTransformation(); + } + + @Override + public String getPropertyName() { + return properties.getName(); + } + + public void setConnectionId() { + isConnectionId = true; + } + + public void updateProperties(ListProperty<Property> properties) { + this.properties = properties; + } + + @Override + public StringProperty getName(int vindex) { + return names.get(vindex); + } + + @Override + public int getWidth() { + int w = 0; + for(int i = 0; i < getLength(); ++i) { + w = Math.max(w, getWidth(i)); + } + + return w; + } + + public boolean isConnectionNodeID() { + return isConnectionId; + } + + @Override + public int getWidth(int vindex) { + Property prop = properties.get(vindex); + if(prop instanceof ListProperty) return ((ListProperty)properties.get(vindex)).size(); + else return 1; + } + + @Override + public int getLength() { + return properties.getSize(); + } + + @Override + public int getColumnIndex(int columnIndex) { + return columnIndex - this.column; + } + + @Override + public Property getProperty(int vindex, int hindex) { + Property prop = properties.get(vindex); + if(prop instanceof ListProperty) { + if(hindex < getWidth(vindex)) return (Property) ((ListProperty)prop).get(hindex); + else return null; + } + else if(hindex < 1) return prop; + else return null; + } + + @Override + public boolean contains(int row, int column) { + return column >= this.column && column < this.column + getWidth() && row >= this.row && row <= this.row + getLength(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/NumberAxis.png b/src/fr/inria/structgraphics/ui/spreadsheet/NumberAxis.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb97343eb6de53d8b3f4fc35f468704f01d9b84 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/spreadsheet/NumberAxis.png differ diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/PropertySet.java b/src/fr/inria/structgraphics/ui/spreadsheet/PropertySet.java new file mode 100644 index 0000000000000000000000000000000000000000..5df1753be44f48333603a910230bfc3036c122fe --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/PropertySet.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.TreeSet; + +public class PropertySet extends TreeSet<MappingProperty> { + + public PropertySet(DataVariable variable) { + for(int i = 0; i < variable.getLength(); ++i) { + for(int j = 0; j< variable.getWidth(); ++j) + add(new MappingProperty(variable.getProperty(i, j))); + } + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/Spreadsheet.java b/src/fr/inria/structgraphics/ui/spreadsheet/Spreadsheet.java new file mode 100644 index 0000000000000000000000000000000000000000..fe3c3cb83d496ff885f3b21b0aa3661e692b44a5 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/Spreadsheet.java @@ -0,0 +1,572 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; + +import java.util.List; +import org.controlsfx.control.spreadsheet.Grid; +import org.controlsfx.control.spreadsheet.GridBase; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetCell.CornerPosition; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.types.ColorProperty; +import fr.inria.structgraphics.types.ConstrainedDoubleProperty; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable.DataType; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; + +import org.controlsfx.control.spreadsheet.SpreadsheetCellType; +import org.controlsfx.control.spreadsheet.SpreadsheetColumn; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +import impl.org.controlsfx.spreadsheet.CellView; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.Property; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; + +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TablePosition; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; + +public class Spreadsheet extends SpreadsheetView { + + private ObservableList<ObservableList<SpreadsheetCell>> rows; + + private DataView dataView; + private AreaInteractor interactor; + private SpreadsheetArea dragOverArea = null; + private MenuItem showAxis, showParentAxis, showLegend, showNode; + + public Spreadsheet(DataView dataView, AreaInteractor interactor) { + super(createEmptyGrid()); + + this.dataView = dataView; + + for(SpreadsheetColumn column: getColumns()){ + column.setPrefWidth(100); + } + + rows = getGrid().getRows(); + + getChildren().get(0).setOnMouseClicked(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + DataVariable variable = interactor.getActiveVariable(); + + interactor.clickedOn(getSelectionModel().getSelectedCells()); + if(variable == null) { + showAxis.setDisable(true); + showParentAxis.setDisable(true); + showLegend.setDisable(true); + } + else { + if((variable.isX() || variable.isY() || variable.isHeight()) && variable.isNested()) { + showAxis.setDisable(false); + if(!variable.scaleShownProperty.get()) + showAxis.setText("Show on inner axis"); + else showAxis.setText("Hide from inner axis"); + } else showAxis.setDisable(true); + + if(!variable.getParentGroup().isEmpty() && ((variable.isX() /*&& !variable.isNested()*/) || variable.isY() || variable.isHeight() || (variable.isWidth() /*&& !variable.isNested()*/) )) { // TODO: Check if there is a parent axis!!!! + showParentAxis.setDisable(false); + if(!variable.scaleShownPropertyOuter.get()) + showParentAxis.setText("Show on outer axis"); + else showParentAxis.setText("Hide from outer axis"); + } else showParentAxis.setDisable(true); + + if(/*(variable.isFill() || variable.isShape() || variable.isStroke() || variable.isThickness()) &&*/ variable.type == DataType.Symbolic) { + showLegend.setDisable(false); + if(!variable.legendShownProperty.get()) + showLegend.setText("Show on legend"); + else showLegend.setText("Hide from legend"); + } else showLegend.setDisable(true); + + if(variable.isID() || variable.isX() || variable.isY() || variable.isHeight() || variable.isWidth() || variable.isWeight() + || variable.isStroke()) { + showNode.setDisable(false); + if(!variable.nodeShownProperty.get()) + showNode.setText("Show on glyphs"); + else showNode.setText("Hide from glyphs"); + } else showNode.setDisable(true); + } + } + }); + + this.interactor = interactor; + } + + + public List<SpreadsheetArea> getAreas() { + return interactor.getAreas(); + } + + public void addArea(SpreadsheetArea area) { + interactor.addArea(area); + } + + public void setCellValues(int row, int column, DataVariable variable) { + for(int i = 0; i < variable.getLength(); ++i) { + for(int j = 0; j < variable.getWidth(i); ++j) + setCellValue(row + i, column + j, i, j, variable); + } + } + + public void setCellValue(int row, int column, int vindex, int hindex, DataVariable variable) { + SpreadsheetCellAdaptable cell = (SpreadsheetCellAdaptable)getCell(row, column); + Property property = variable.getProperty(vindex, hindex); + + cell.setType(SpreadsheetCellType.OBJECT); + cell.setGraphic(null); + + // TODO: For now, I only handle double properties.... I will come back here later and fix the rest of the types!!!! + // TODO: This is tricky!!!! + cell.setPropertyListener(property, new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + //if(cell.isPropertyListenerLocked()) return; + + cell.lockCellListener(true); + String value = variable.transformation.get().toData(property.getValue()); + if(value!=null) cell.itemProperty().set(value); + cell.lockCellListener(false); + + if(property instanceof ColorProperty) cell.setStyle("-fx-background-color: #" + property.getValue().toString().substring(2) + ";"); + } + }); + + cell.itemProperty().set(variable.transformation.get().toData(property.getValue())); // Init the value.... + if(property instanceof ColorProperty) cell.setStyle("-fx-background-color: #" + property.getValue().toString().substring(2) + ";"); + + + cell.setCellListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + + if(cell.isCellListenerLocked()) return; + //cell.lockPropertyListener(true); + + if (property instanceof ConstrainedDoubleProperty) { + ((ConstrainedDoubleProperty)property).updateValue((double) variable.transformation.get().fromData(property, cell.getText())); + } + else property.setValue(variable.transformation.get().fromData(property, cell.getText())); + + //cell.lockPropertyListener(false); + } + }); + + /* + cell.setCellListener(new ChangeListener<Object>() { + @Override + public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) { + + if(cell.isCellListenerLocked()) return; + //cell.lockPropertyListener(true); + + if (property instanceof ConstrainedDoubleProperty) { + ((ConstrainedDoubleProperty)property).updateValue((double) variable.transformation.get().fromData(property, oldValue, newValue)); + } + else property.setValue(variable.transformation.get().fromData(property, oldValue, newValue)); + + //cell.lockPropertyListener(false); + } + });*/ + + } + + + public void setCellLabel(int row, int column, int vindex, DataVariable variable) { + SpreadsheetCellAdaptable cell = (SpreadsheetCellAdaptable)getCell(row, column); + cell.setType(SpreadsheetCellType.STRING); + + Circle circle = new Circle(0,0,5); + circle.setStroke(Color.CORNFLOWERBLUE); + circle.setFill(null); + cell.setGraphic(circle); + + cell.setStyle("-fx-background-color: #cece0d;"); + + cell.itemProperty().set(variable.getName(vindex).get()); + cell.setCellListener(new InvalidationListener() { + /* + @Override + public void changed(ObservableValue observable, Object oldValue Object newValue) { + StringProperty name = variable.getName(vindex); + if(name != null) name.set(newValue.toString()); + }*/ + + @Override + public void invalidated(Observable observable) { + StringProperty name = variable.getName(vindex); + if(name != null) name.set(cell.getText()); + } + }); + variable.getName(vindex).addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + cell.itemProperty().set(newValue); + } + }); + } + + public void setCellLabel(int row, int column, String newName) { + SpreadsheetCellAdaptable cell = (SpreadsheetCellAdaptable)getCell(row, column); + cell.itemProperty().set(newName); + } + + public void clear(int row, int column) {// System.err.println("clear " + row + ", " + column); + SpreadsheetCellAdaptable cell = (SpreadsheetCellAdaptable)getCell(row, column); + cell.setCellListener(null); + cell.setPropertyListener(null, null); + cell.setGraphic(null); + + cell.setType(SpreadsheetCellType.STRING); + cell.setStyle("-fx-background-color: #ffffff;"); + cell.itemProperty().set(""); + + cell.deactivateCorner(CornerPosition.BOTTOM_LEFT); + cell.deactivateCorner(CornerPosition.BOTTOM_RIGHT); + cell.deactivateCorner(CornerPosition.TOP_LEFT); + cell.deactivateCorner(CornerPosition.TOP_RIGHT); + } + + + private static Grid createEmptyGrid() { + GridBase grid = new GridBase(800, 26); + + ObservableList<ObservableList<SpreadsheetCell>> rows = FXCollections.observableArrayList(); + for (int row = 0; row < grid.getRowCount(); ++row) { + final ObservableList<SpreadsheetCell> list = FXCollections.observableArrayList(); + for (int column = 0; column < grid.getColumnCount(); ++column) { + //list.add(SpreadsheetCellType.DOUBLE.createCell(row, column, 1, 1, null)); + SpreadsheetCellAdaptable cell = new SpreadsheetCellAdaptable(row, column, 1, 1, SpreadsheetCellType.OBJECT); + + //cell.addEventHandler(eventType, eventHandler); + + list.add(cell); + } + rows.add(list); + } + grid.setRows(rows); + + return grid; + } + + + public static int toColumnIndex(CellView cellView) { + return cellView.getTableView().getColumns().indexOf(cellView.getTableColumn()); + } + + public SpreadsheetCellAdaptable getCell(int row, int column) { + return (SpreadsheetCellAdaptable)rows.get(row).get(column); + } + + public SpreadsheetCellAdaptable getCell(int row, String columnName) { + return getCell(row, columnName.charAt(0) - 'A'); + } + + public SpreadsheetCellAdaptable toCell(CellView cellView) { + return getCell(cellView.getIndex(), cellView.getTableColumn().getText()); + } + + public void dragOver(CellView cellView, int width, int height) { + if(dragOverArea != null) { + dragOverArea.updateDrag(cellView); + } + else dragOverArea = new SpreadsheetArea(this, cellView, width, height); + + dragOverArea.updateDrag(cellView); + } + + public void dragEnded(Draggable draggable) { + if(dragOverArea != null) dragOverArea.dragEnded(); + dragOverArea.setContent(draggable); + + if(dragOverArea != null) { + interactor.addArea(dragOverArea); + dragOverArea = null; + } + } + + public void reset() { + dragOverArea = null; + + setGrid(createEmptyGrid()); + + for(SpreadsheetColumn column: getColumns()){ + column.setPrefWidth(100); + } + + rows = getGrid().getRows(); + interactor.reset(); + } + + + private void markVariable(DataVariable variable, SpreadsheetCell.CornerPosition position, boolean activate) { + int row = variable.row(); + int column = variable.column(); + int width = variable.getWidth(); + + for(int i = 0; i < width; ++i) { + SpreadsheetCell cell = getGrid().getRows().get(row).get(column + i); + if(activate) cell.activateCorner(position); + else cell.deactivateCorner(position); + } + } + + private void showOnGraph() { + showOnGraph(interactor.getActiveVariable()); + } + + private void showOnParentGraph() { + showOnParentGraph(interactor.getActiveVariable()); + } + + private void showOnLegend() { + showOnLegend(interactor.getActiveVariable()); + } + + private void showOnNode() { + showOnNode(interactor.getActiveVariable()); + } + + public void showOnGraph(DataVariable variable) { + if(variable != null) { + markVariable(variable, SpreadsheetCell.CornerPosition.TOP_LEFT, !variable.scaleShownProperty.get()); + + List<VisBody> collections = variable.getLevelGroups(); + for(VisBody collection: collections) ((VisCollection)collection).showVariableOnAxis(variable, false); + + variable.scaleShownProperty.set(!variable.scaleShownProperty.get()); + + if(variable.scaleShownProperty.get()) { + variable.scaleShownProperty.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + markVariable(variable, SpreadsheetCell.CornerPosition.TOP_LEFT, false); + variable.scaleShownProperty.removeListener(this); + } + }); + } + } + } + + public void showOnParentGraph(DataVariable variable) { + if(variable != null) { + markVariable(variable, SpreadsheetCell.CornerPosition.BOTTOM_LEFT, !variable.scaleShownPropertyOuter.get()); + + List<VisBody> collections = variable.getParentGroup(); + for(VisBody collection: collections) { + ((VisCollection)collection).showVariableOnAxis(variable, true); + } + + variable.scaleShownPropertyOuter.set(!variable.scaleShownPropertyOuter.get()); + + if(variable.scaleShownPropertyOuter.get()) { + variable.scaleShownPropertyOuter.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + markVariable(variable, SpreadsheetCell.CornerPosition.BOTTOM_LEFT, false); + variable.scaleShownPropertyOuter.removeListener(this); + } + }); + } + } + } + + public void showOnLegend(DataVariable variable) { + if(variable != null) { + markVariable(variable, SpreadsheetCell.CornerPosition.TOP_RIGHT, !variable.legendShownProperty.get()); + + VisBody collection = variable.getCollection(); + ((VisCollection)collection).showVariableOnLegend(variable); + + variable.legendShownProperty.set(!variable.legendShownProperty.get()); + } + } + + public void showOnNode(DataVariable variable) { + if(variable != null) { + markVariable(variable, SpreadsheetCell.CornerPosition.BOTTOM_RIGHT, !variable.nodeShownProperty.get()); + variable.nodeShownProperty.set(!variable.nodeShownProperty.get()); + + if(variable.isWeight() && variable.getCollection() instanceof LineConnectedCollection) { + ((LineConnectedCollection)variable.getCollection()).showLabels(variable); + } else { + for(int i=0; i < variable.getLength(); ++i) { + for(int j = 0; j < variable.getWidth(); ++j) { + Property property = variable.getProperty(i,j); + if(property.getBean() instanceof Mark) { + ((Mark)property.getBean()).showLabel(variable, property); + } + } + } + } + } + } + + + /** + * Create a menu on rightClick with two options: Copy/Paste This can be + * overridden by developers for custom behavior. + * + * @return the ContextMenu to use. + */ + public ContextMenu getSpreadsheetViewContextMenu() { + final ContextMenu contextMenu = new ContextMenu(); + + showAxis = new MenuItem(localize(asKey("spreadsheet.view.menu.axis"))); + showAxis.setGraphic(new ImageView(new Image(Spreadsheet.class.getResourceAsStream("NumberAxis.png")))); + showAxis.setAccelerator(new KeyCodeCombination(KeyCode.A, KeyCombination.SHORTCUT_DOWN)); + showAxis.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + showOnGraph(); + } + }); + + showParentAxis = new MenuItem(localize(asKey("spreadsheet.view.menu.parentaxis"))); + showParentAxis.setGraphic(new ImageView(new Image(Spreadsheet.class.getResourceAsStream("NumberAxis.png")))); + showParentAxis.setAccelerator(new KeyCodeCombination(KeyCode.E, KeyCombination.SHORTCUT_DOWN)); + showParentAxis.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + showOnParentGraph(); + } + }); + + showLegend = new MenuItem(localize(asKey("spreadsheet.view.menu.legend"))); + showLegend.setGraphic(new ImageView(new Image(Spreadsheet.class.getResourceAsStream("legend.png")))); + showLegend.setAccelerator(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN)); + showLegend.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + showOnLegend(); + } + }); + + showNode = new MenuItem(localize(asKey("spreadsheet.view.menu.node"))); + showNode.setGraphic(new ImageView(new Image(Spreadsheet.class.getResourceAsStream("legend.png")))); + showNode.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN)); + showNode.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + showOnNode(); + } + }); + + + final MenuItem copyItem = new MenuItem(localize(asKey("spreadsheet.view.menu.copy"))); //$NON-NLS-1$ + copyItem.setGraphic(new ImageView(new Image(SpreadsheetView.class + .getResourceAsStream("copySpreadsheetView.png")))); //$NON-NLS-1$ + copyItem.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN)); + copyItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + copyClipboard(); + } + }); + + final MenuItem pasteItem = new MenuItem(localize(asKey("spreadsheet.view.menu.paste"))); //$NON-NLS-1$ + pasteItem.setGraphic(new ImageView(new Image(SpreadsheetView.class + .getResourceAsStream("pasteSpreadsheetView.png")))); //$NON-NLS-1$ + pasteItem.setAccelerator(new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN)); + pasteItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + pasteClipboard(); + } + }); + + final Menu cornerMenu = new Menu(localize(asKey("spreadsheet.view.menu.comment"))); //$NON-NLS-1$ + cornerMenu.setGraphic(new ImageView(new Image(SpreadsheetView.class + .getResourceAsStream("comment.png")))); //$NON-NLS-1$ + + final MenuItem topLeftItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.top-left"))); //$NON-NLS-1$ + topLeftItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + if(cell.isCornerActivated(SpreadsheetCell.CornerPosition.TOP_LEFT)) + cell.deactivateCorner(SpreadsheetCell.CornerPosition.TOP_LEFT); + else cell.activateCorner(SpreadsheetCell.CornerPosition.TOP_LEFT); + + } + }); + final MenuItem topRightItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.top-right"))); //$NON-NLS-1$ + topRightItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + if(cell.isCornerActivated(SpreadsheetCell.CornerPosition.TOP_RIGHT)) + cell.deactivateCorner(SpreadsheetCell.CornerPosition.TOP_RIGHT); + else cell.activateCorner(SpreadsheetCell.CornerPosition.TOP_RIGHT); + } + }); + final MenuItem bottomRightItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.bottom-right"))); //$NON-NLS-1$ + bottomRightItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + if(cell.isCornerActivated(SpreadsheetCell.CornerPosition.BOTTOM_RIGHT)) + cell.deactivateCorner(SpreadsheetCell.CornerPosition.BOTTOM_RIGHT); + else cell.activateCorner(SpreadsheetCell.CornerPosition.BOTTOM_RIGHT); + } + }); + final MenuItem bottomLeftItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.bottom-left"))); //$NON-NLS-1$ + bottomLeftItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + if(cell.isCornerActivated(SpreadsheetCell.CornerPosition.BOTTOM_LEFT)) + cell.deactivateCorner(SpreadsheetCell.CornerPosition.BOTTOM_LEFT); + else cell.activateCorner(SpreadsheetCell.CornerPosition.BOTTOM_LEFT); + } + }); + + // I use corner annotations for marking visualized variables + // cornerMenu.getItems().addAll(topLeftItem, topRightItem, bottomRightItem, bottomLeftItem); + + contextMenu.getItems().addAll(showAxis, showParentAxis, showLegend, showNode, copyItem, pasteItem /*, cornerMenu*/); + return contextMenu; + } + + + public void clean() { + interactor.reset(); + } + + public void update() { + interactor.update(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/SpreadsheetArea.java b/src/fr/inria/structgraphics/ui/spreadsheet/SpreadsheetArea.java new file mode 100644 index 0000000000000000000000000000000000000000..78d464b5db76ac21d79ca034b9ab7b7d5e8ddef7 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/SpreadsheetArea.java @@ -0,0 +1,319 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import java.util.ArrayList; +import java.util.Map; + +import org.controlsfx.control.spreadsheet.Grid; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.ColorProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.Draggable; +import fr.inria.structgraphics.ui.Draggable.Type; +import fr.inria.structgraphics.ui.viscanvas.groupings.PropertyStructure; +import impl.org.controlsfx.spreadsheet.CellView; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.Property; +import javafx.scene.paint.Paint; + +public class SpreadsheetArea { + private int x, y, w, h; + + private Spreadsheet sheet; + private AreaContent content; + + private AreaSourceInfo info; + + public SpreadsheetArea(Spreadsheet sheet, int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + + this.sheet = sheet; + } + + public SpreadsheetArea(Spreadsheet sheet, CellView cellView, int w, int h) { + this(sheet, cellView.getIndex(), Spreadsheet.toColumnIndex(cellView), w, h); + } + + public void dragEnded() { + for(int row = x; row < x + w; ++row) { + for(int column = y; column < y + h; ++column) { + sheet.getCell(row, column).recoverStyle(); + } + } + } + + public void updateDrag(CellView cellView) { + int x_ = cellView.getIndex(); + int y_ = Spreadsheet.toColumnIndex(cellView); + Grid grid = sheet.getGrid(); + + for(int i = Math.min(x, x_); i < Math.max(x, x_) + w && i < grid.getRowCount(); ++i) { + for(int j = Math.min(y, y_); j < Math.max(y, y_) + h && j < grid.getColumnCount(); ++j) { + if(i >= x_ && i < x_ + w && j >= y_ && j < y_ + h) { + SpreadsheetCellAdaptable cell = sheet.getCell(i, j); + cell.saveStyle(); + cell.setStyle("-fx-background-color: #eeff66;"); + } + else { + sheet.getCell(i, j).recoverStyle(); + } + } + } + + x = x_; + y = y_; + + } + + public int getStartRow() { + return x; + } + + public int getStartColumn() { + return y; + } + + public int nrows() { + return h; + } + + public int ncolumns() { + return w; + } + + public DataVariable getVariable(int row, int column) { + return content.getVariable(row, column); + } + + public int getVIndex(DataVariable variable, int column) { + return content.getVIndex(variable, column); + } + + public boolean contains(int row, int column) { + if(row >= x && row < row + h && column >= y && column < y + w) return true; + else return false; + } + + public void update(CellView cellView) { + x = cellView.getIndex(); + y = Spreadsheet.toColumnIndex(cellView); + } + + + public void setContent(Draggable draggable) { + info = new AreaSourceInfo(draggable); + refresh(); + } + + public void setInfo(AreaSourceInfo info) { + this.info = info; + refresh(); + } + + + public void clearSketcherLabels() { + for(DataVariable variable : content.getVariables()) { + if(variable.scaleShownProperty.get()) sheet.showOnGraph(variable); + if(variable.scaleShownPropertyOuter.get()) sheet.showOnParentGraph(variable); + if(variable.legendShownProperty.get()) sheet.showOnLegend(variable); + if(variable.nodeShownProperty.get()) sheet.showOnNode(variable); + } + } + + public void clear() { + for(int row = x; row < x + h + 1; ++row) { + for(int column = y; column < y + w; ++column) { + sheet.clear(row, column); + } + } + } + + public Type getType() { + return info.getType(); + } + + public boolean refresh() { + if(!info.sourceExists()) { + info = null; + return false; + } + + switch(info.getType()) { + case Value: + if(content != null) return true; + + VisBody visbody = info.getVisBody(); + + final Property property = info.getProperty(); + DataVariable variable = new VectorVariable(visbody, x, y, property); + content = new AreaContent(property.getName(), variable); + + /////////////////////////// + sheet.setCellLabel(x, y, 0, variable); + sheet.setCellValues(x + 1, y, variable); + + variable.setInvalidationListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + Object val = property.getValue(); + if(property instanceof DoubleProperty) ((DoubleProperty)property).setValue((Number)val); + else if(property instanceof ColorProperty) ((ColorProperty)property).setValue((Paint)val); + else property.setValue(val); + } + }); + + return true; + + case Column: + case Table: + if(content == null) content = new AreaContent(); + + visbody = info.getVisBody(); + Map<PropertyName, FlexibleListProperty> propertiesMap = null; + + /// TODO: This needs more work, no? + if(info.isNetwork) { + propertiesMap = ((LineConnectedCollection)visbody).getFlowConnections().getCompactVariableList(info.getNames()); + } else { + PropertyStructure structure = visbody.getChildPropertyStructure(); + propertiesMap = info.isWide() ? structure.getVariableList(info.getNames()) : structure.getFullVariableList(info.getNames()); + } + + // TODO: UPdata the properties within the variables!!!! + int j = 0; + int rows = 0; + for(FlexibleListProperty col: propertiesMap.values()) { + variable = content.getVariable(col.getName()); + + if(!info.isWide()) { + if(variable == null) { + variable = new ColumnVariable(visbody, x, y + j, col); + content.addVariable(col.getName(), variable); + } else { + variable.updatePosition(x, y + j); + ((ColumnVariable)variable).updateProperties(col); + variable.transformation.get().refresh(); + } + + sheet.setCellLabel(x, y + j, 0, variable); + sheet.setCellValues(x + 1, y + j, variable); + j++; + rows = Math.max(rows, variable.getLength()); + + } else { + if(variable == null) { + variable = new MultiColumnVariable(visbody, x, y + j, col); + content.addVariable(col.getName(), variable); + } else { + variable.updatePosition(x, y + j); + ((MultiColumnVariable)variable).updateProperties(col); + variable.transformation.get().refresh(); + } + + for(int k = 0; k < variable.getWidth(); ++k) { + sheet.setCellLabel(x, y + j + k, k, variable); + } + + // This is just to handle flow the names of connection ids + if(info.isNetwork && variable.isID()) { + ((MultiColumnVariable)variable).setConnectionId(); + sheet.setCellLabel(x, y + j, "source"); + sheet.setCellLabel(x, y + j + 1, "destination"); + } + + sheet.setCellValues(x + 1, y + j, variable); + j+=variable.getWidth(); + rows = Math.max(rows, variable.getLength()); + } + + + variable.setInvalidationListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + for(Property prop: col) { + if(prop instanceof ListProperty) { + ListProperty<Property> list = (ListProperty<Property>)prop; + for(Property prop_: list) { + // TODO: an uggly way to enforce the property change!!!!! + Object val = prop_.getValue(); + if(prop_ instanceof DoubleProperty) ((DoubleProperty)prop_).setValue((Number)val); + else if(prop_ instanceof ColorProperty) ((ColorProperty)prop_).setValue((Paint)val); + else prop_.setValue(val); + } + } else { + // TODO: an uggly way to enforce the property change!!!!! + Object val = prop.getValue(); + if(prop instanceof DoubleProperty) ((DoubleProperty)prop).setValue((Number)val); + else if(prop instanceof ColorProperty) ((ColorProperty)prop).setValue((Paint)val); + else prop.setValue(val); + } + } + } + }); + + // Update Labels on Nodes + for(Property prop: col) { + if(prop instanceof ListProperty) { + ListProperty<Property> list = (ListProperty<Property>)prop; + for(Property prop_: list) { + if(prop_.getBean() instanceof ShapeMark) { + ShapeMark mark = (ShapeMark)prop_.getBean(); + if(variable.nodeShownProperty.get() && !mark.isLabelShown(variable)) + mark.showLabel(variable, prop_); + } + } + } else { + if(prop.getBean() instanceof ShapeMark) { + ShapeMark mark = (ShapeMark)prop.getBean(); + if(variable.nodeShownProperty.get() && !mark.isLabelShown(variable)) + mark.showLabel(variable, prop); + } + } + } + + } + + w = j; + h = rows; + + return true; + } + + return false; + } + + + public Container getSource() { + return info.source; + } + + public VisBody getVisBody() { + return info.visbody; + } + + public boolean isWide() { + return info.wide; + } + + public boolean isNetwork() { + return info.isNetwork; + } + + public ArrayList<PropertyName> getPropertyNames() { + return info.names; + } + + public AreaContent getAreaContent() { + return content; + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/SpreadsheetCellAdaptable.java b/src/fr/inria/structgraphics/ui/spreadsheet/SpreadsheetCellAdaptable.java new file mode 100644 index 0000000000000000000000000000000000000000..4c5ce711dd4fe7fb1b5cb1c76ccde366997d1a29 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/SpreadsheetCellAdaptable.java @@ -0,0 +1,696 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import com.sun.javafx.event.EventHandlerManager; + +import impl.org.controlsfx.spreadsheet.CellView; + +import java.util.Objects; +import java.util.Optional; + +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetCellType; + +import javafx.beans.InvalidationListener; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableSet; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.event.EventType; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.PickResult; + +/** + * The SpreadsheetCells serve as model for the {@link SpreadsheetView}. <br> + * You will provide them when constructing a {@link Grid}. + * + * <br> + * <h3>SpreadsheetCell Types</h3> Each SpreadsheetCell has its own + * {@link SpreadsheetCellType} which has its own {@link SpreadsheetCellEditor} + * in order to control very closely the possible modifications. + * + * <p> + * Different {@link SpreadsheetCellType SpreadsheetCellTypes} are available + * depending on the data you want to represent in your {@link SpreadsheetView}. + * You can use the different static method provided in + * {@link SpreadsheetCellType} in order to create the specialized + * SpreadsheetCell that suits your need. + * + * + * <br> + * + * <p> + * If you want to create a SpreadsheetCell of your own, you simply have to + * use one of the provided constructor. Usually you will let your {@link SpreadsheetCellType} + * create the cells. For example + * {@link SpreadsheetCellType.StringType#createCell(int, int, int, int, java.lang.String) }. + * You will also have to provide a custom {@link SpreadsheetCellEditor}. + * + * <h2>Configuration</h2> + * You will have to indicate the coordinates of the cell together with the + * {@link #setRowSpan(int) row} and {@link #setColumnSpan(int) column} span. You + * can specify if you want the cell to be editable or not using + * {@link #setEditable(boolean)}. Be advised that a cell with a rowSpan means + * that the cell will replace all the cells situated in the rowSpan range. Same + * with the column span. + * <br> + * So the best way to handle spanning is to fill your grid + * with unique cells, and then call at the end {@link GridBase#spanColumn(int, int, int)} + * or {@link GridBase#spanRow(int, int, int)}. These methods will handle the span + * for you. + * + * <br> + * + * <h3>Format</h3> + * Your cell can have its very own format. If you want to display some dates + * with different format, you just have to create a unique + * {@link SpreadsheetCellType} and then specify for each cell their format with + * {@link #setFormat(String)}. You will then have the guaranty that all your + * cells will have a LocalDate as a value, but the value will be displayed + * differently for each cell. This will also guaranty that copy/paste and other + * operation will be compatible since every cell will share the same + * {@link SpreadsheetCellType}. <br> + * Here an example : <br> + * + * + * <pre> + * SpreadsheetCell cell = SpreadsheetCellType.DATE.createCell(row, column, rowSpan, colSpan, + * LocalDate.now().plusDays((int) (Math.random() * 10))); // Random value + * // given here + * final double random = Math.random(); + * if (random < 0.25) { + * cell.setFormat("EEEE d"); + * } else if (random < 0.5) { + * cell.setFormat("dd/MM :YY"); + * } else { + * cell.setFormat("dd/MM/YYYY"); + * } + * </pre> + * + * <center><img src="dateFormat.PNG" alt="SpreadsheetCellBase with custom format"></center> + * + * <h3>Graphic</h3> + * Each cell can have a graphic to display next to the text in the cells. Just + * use the {@link #setGraphic(Node)} in order to specify the graphic you want. + * If you specify an {@link ImageView}, the SpreadsheetView will try to resize it in + * order to fit the space available in the cell. + * + * For example : + * + * <pre> + * cell.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("icons/exclamation.png")))); + * </pre> + * + * <center><img src="graphicNodeToCell.png" alt="SpreadsheetCellBase with graphic"></center> <br> + * In addition to that, you can also specify another graphic property to your + * cell with {@link #activateCorner(org.controlsfx.control.spreadsheet.SpreadsheetCell.CornerPosition) }. + * This allow you to activate or deactivate some graphics on the cell in every + * corner. Right now it's a little red triangle but you can modify this in your CSS by + * using the "<b>cell-corner</b>" style class. + * + * <pre> + * .cell-corner.top-left{ + * -fx-background-color: red; + * -fx-shape : "M 0 0 L 1 0 L 0 1 z"; + * } + * </pre> + * + * <center><img src="triangleCell.PNG" alt="SpreadsheetCellBase with a styled cell-corner"></center> + * + * + * <br> + * You can also customize the tooltip of your SpreadsheetCell by specifying one + * with {@link #setTooltip(java.lang.String) }. + * + * <h3>Style with CSS</h3> + * You can style your cell by specifying some styleClass with + * {@link #getStyleClass()}. You just have to create and custom that class in + * your CSS stylesheet associated with your {@link SpreadsheetView}. Also note + * that all {@link SpreadsheetCell} have a "<b>spreadsheet-cell</b>" styleClass + * added by default. Here is a example :<br> + * + * <pre> + * cell.getStyleClass().add("row_header"); + * </pre> + * + * And in the CSS: + * + * <pre> + * .spreadsheet-cell.row_header{ + * -fx-background-color: #b4d4ad ; + * -fx-background-insets: 0, 0 1 1 0; + * -fx-alignment: center; + * } + * </pre> + * + * <h3>Examples</h3> + * Here is an example that uses all the pre-built {@link SpreadsheetCellType} + * types. The generation is random here so you will want to replace the logic to + * suit your needs. + * + * <pre> + * private SpreadsheetCell<?> generateCell(int row, int column, int rowSpan, int colSpan) { + * List<String> stringListTextCell = Arrays.asList("Shanghai","Paris","New York City","Bangkok","Singapore","Johannesburg","Berlin","Wellington","London","Montreal"); + * final double random = Math.random(); + * if (random < 0.10) { + * List<String> stringList = Arrays.asList("China","France","New Zealand","United States","Germany","Canada"); + * cell = SpreadsheetCellType.LIST(stringList).createCell(row, column, rowSpan, colSpan, stringList.get((int) (Math.random() * 6))); + * } else if (random >= 0.10 && random < 0.25) { + * cell = SpreadsheetCellType.STRING.createCell(row, column, rowSpan, colSpan,stringListTextCell.get((int)(Math.random()*10))); + * } else if (random >= 0.25 && random < 0.75) { + * cell = SpreadsheetCellType.DOUBLE.createCell(row, column, rowSpan, colSpan,(double)Math.round((Math.random()*100)*100)/100); + * } else { + * cell = SpreadsheetCellType.DATE.createCell(row, column, rowSpan, colSpan, LocalDate.now().plusDays((int)(Math.random()*10))); + * } + * return cell; + * } + * </pre> + * + * @see SpreadsheetView + * @see SpreadsheetCellEditor + * @see SpreadsheetCellType + */ +public class SpreadsheetCellAdaptable implements SpreadsheetCell, EventTarget{ + + /*************************************************************************** + * + * Private Fields + * + **************************************************************************/ + + //The Bit position for the editable Property. + private static final int EDITABLE_BIT_POSITION = 4; + private static final int WRAP_BIT_POSITION = 5; + private SpreadsheetCellType type; + private final int row; + private final int column; + private int rowSpan; + private int columnSpan; + private final StringProperty format; + private final StringProperty text; + private final StringProperty styleProperty; + private final ObjectProperty<Node> graphic; + private String tooltip; + /** + * This variable handles all boolean values of this SpreadsheetCell inside + * its bits. Instead of using regular boolean, we use that int so that we + * can reduce memory usage to the bare minimum. + */ + private int propertyContainer = 0; + private final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); + + private ObservableSet<String> styleClass; + + // Added by Theophanis Tsandilas + private /*ChangeListener*/ InvalidationListener changeListener = null; // Listener that updates the property when the cell changes + private Property property; // Property to which the cell is linked + private InvalidationListener propertyListener = null; // Listener that updates the cell when the property changes + + + /*************************************************************************** + * + * Constructor + * + **************************************************************************/ + + /** + * Constructs a SpreadsheetCell with the given configuration. + * Use the {@link SpreadsheetCellType#OBJECT} type. + * @param row + * @param column + * @param rowSpan + * @param columnSpan + */ + public SpreadsheetCellAdaptable(final int row, final int column, final int rowSpan, final int columnSpan) { + this(row, column, rowSpan, columnSpan, SpreadsheetCellType.OBJECT); + } + + /** + * Constructs a SpreadsheetCell with the given configuration. + * + * @param row + * @param column + * @param rowSpan + * @param columnSpan + * @param type + */ + public SpreadsheetCellAdaptable(final int row, final int column, final int rowSpan, final int columnSpan, + final SpreadsheetCellType<?> type) { + this.row = row; + this.column = column; + this.rowSpan = rowSpan; + this.columnSpan = columnSpan; + this.type = type; + text = new SimpleStringProperty(""); //$NON-NLS-1$ + format = new SimpleStringProperty(""); //$NON-NLS-1$ + graphic = new SimpleObjectProperty<>(); + format.addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) { + updateText(); + } + }); + //Editable is true at the initialisation + setEditable(true); + getStyleClass().add("spreadsheet-cell"); //$NON-NLS-1$ + styleProperty = new SimpleStringProperty(); + + } + + + public void setType(SpreadsheetCellType<?> type) { + this.type = type; + } + + /*************************************************************************** + * + * Public Methods + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override + public boolean match(SpreadsheetCell cell) { + return type.match(cell); + } + + // --- item + private final ObjectProperty<Object> item = new SimpleObjectProperty<Object>(this, "item") { //$NON-NLS-1$ + @Override + protected void invalidated() { + updateText(); + } + }; + + /** {@inheritDoc} */ + @Override + public final void setItem(Object value) { + if (isEditable()) + item.set(value); + } + + /** {@inheritDoc} */ + @Override + public final Object getItem() { + return item.get(); + } + + /** {@inheritDoc} */ + @Override + public final ObjectProperty<Object> itemProperty() { + return item; + } + + /** {@inheritDoc} */ + @Override + public final boolean isEditable() { + return isSet(EDITABLE_BIT_POSITION); + } + + /** {@inheritDoc} */ + @Override + public final void setEditable(boolean editable) { + if(setMask(editable, EDITABLE_BIT_POSITION)){ + Event.fireEvent(this, new Event(EDITABLE_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapText(){ + return isSet(WRAP_BIT_POSITION); + } + + /** {@inheritDoc} */ + @Override + public void setWrapText(boolean wrapText) { + if (setMask(wrapText, WRAP_BIT_POSITION)) { + Event.fireEvent(this, new Event(WRAP_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public final StringProperty formatProperty() { + return format; + } + + /** {@inheritDoc} */ + @Override + public final String getFormat() { + return format.get(); + } + + /** {@inheritDoc} */ + @Override + public final void setFormat(String format) { + formatProperty().set(format); + updateText(); + } + + /** {@inheritDoc} */ + @Override + public final ReadOnlyStringProperty textProperty() { + return text; + } + + /** {@inheritDoc} */ + @Override + public final String getText() { + return text.get(); + } + + /** {@inheritDoc} */ + @Override + public final SpreadsheetCellType getCellType() { + return type; + } + + /** {@inheritDoc} */ + @Override + public final int getRow() { + return row; + } + + /** {@inheritDoc} */ + @Override + public final int getColumn() { + return column; + } + + /** {@inheritDoc} */ + @Override + public final int getRowSpan() { + return rowSpan; + } + + /** {@inheritDoc} */ + @Override + public final void setRowSpan(int rowSpan) { + this.rowSpan = rowSpan; + } + + /** {@inheritDoc} */ + @Override + public final int getColumnSpan() { + return columnSpan; + } + + /** {@inheritDoc} */ + @Override + public final void setColumnSpan(int columnSpan) { + this.columnSpan = columnSpan; + } + + /** {@inheritDoc} */ + @Override + public final ObservableSet<String> getStyleClass() { + if (styleClass == null) { + styleClass = FXCollections.observableSet(); + } + return styleClass; + } + + /** {@inheritDoc} */ + @Override + public void setStyle(String style){ + styleProperty.set(style); + } + + /** {@inheritDoc} */ + @Override + public String getStyle(){ + return styleProperty.get(); + } + + /** {@inheritDoc} */ + @Override + public StringProperty styleProperty(){ + return styleProperty; + } + + /** {@inheritDoc} */ + @Override + public ObjectProperty<Node> graphicProperty() { + return graphic; + } + + /** {@inheritDoc} */ + @Override + public void setGraphic(Node graphic) { + this.graphic.set(graphic); + } + + /** {@inheritDoc} */ + @Override + public Node getGraphic() { + return graphic.get(); + } + + /** {@inheritDoc} */ + @Override + public Optional<String> getTooltip() { + return Optional.ofNullable(tooltip); + } + + /** + * Set a new tooltip for this cell. + * @param tooltip + */ + public void setTooltip(String tooltip){ + this.tooltip = tooltip; + } + + /** {@inheritDoc} */ + @Override + public void activateCorner(CornerPosition position) { + if(setMask(true, getCornerBitNumber(position))){ + Event.fireEvent(this, new Event(CORNER_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public void deactivateCorner(CornerPosition position) { + if(setMask(false, getCornerBitNumber(position))){ + Event.fireEvent(this, new Event(CORNER_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isCornerActivated(CornerPosition position) { + return isSet(getCornerBitNumber(position)); + } + + /** {@inheritDoc} */ + @Override + public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + return tail.append(eventHandlerManager); + } + + /*************************************************************************** + * + * Overridden Methods + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override + public String toString() { + return "cell[" + row + "][" + column + "]" + rowSpan + "-" + columnSpan; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof SpreadsheetCell)) + return false; + + final SpreadsheetCell otherCell = (SpreadsheetCell) obj; + return otherCell.getRow() == row && otherCell.getColumn() == column + && Objects.equals(otherCell.getText(), getText()) + && rowSpan == otherCell.getRowSpan() + && columnSpan == otherCell.getColumnSpan() + && Objects.equals(getStyleClass(), otherCell.getStyleClass()); + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + column; + result = prime * result + row; + result = prime * result + rowSpan; + result = prime * result + columnSpan; + result = prime * result + Objects.hashCode(getText()); + result = prime * result + Objects.hashCode(getStyleClass()); + return result; + } + + /** + * Registers an event handler to this SpreadsheetCell. The SpreadsheetCell class allows + * registration of listeners which will be notified when a corner state of + * the editable state of this SpreadsheetCell have changed. + * + * @param eventType the type of the events to receive by the handler + * @param eventHandler the handler to register + * @throws NullPointerException if the event type or handler is null + */ + @Override + public void addEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler) { + eventHandlerManager.addEventHandler(eventType, eventHandler); + } + + /** + * Unregisters a previously registered event handler from this SpreadsheetCell. One + * handler might have been registered for different event types, so the + * caller needs to specify the particular event type from which to + * unregister the handler. + * + * @param eventType the event type from which to unregister + * @param eventHandler the handler to unregister + * @throws NullPointerException if the event type or handler is null + */ + @Override + public void removeEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler) { + eventHandlerManager.removeEventHandler(eventType, eventHandler); + } + + /*************************************************************************** + * + * Private Implementation + * + **************************************************************************/ + + /** + * Update the text for the SpreadsheetView. + */ + @SuppressWarnings("unchecked") + private void updateText() { + if(getItem() == null){ + text.setValue(""); //$NON-NLS-1$ + }else if (!("").equals(getFormat())) { //$NON-NLS-1$ + text.setValue(type.toString(getItem(), getFormat())); + } else { + text.setValue(type.toString(getItem())); + } + } + + /** + * Return the Bit position for each corner. + * @param position + * @return + */ + private int getCornerBitNumber(CornerPosition position) { + switch (position) { + case TOP_LEFT: + return 0; + + case TOP_RIGHT: + return 1; + + case BOTTOM_RIGHT: + return 2; + + case BOTTOM_LEFT: + default: + return 3; + } + } + + /** + * Set the specified bit position at the value specified by flag. + * @param flag + * @param position + * @return whether a change has really occured. + */ + private boolean setMask(boolean flag, int position) { + int oldCorner = propertyContainer; + if (flag) { + propertyContainer |= (1 << position); + } else { + propertyContainer &= ~(1 << position); + } + return propertyContainer != oldCorner; + } + + /** + * @param mask + * @param position + * @return whether the specified bit position is true. + */ + private boolean isSet(int position) { + return (propertyContainer & (1 << position)) != 0; + } + + private boolean saved = false; + private String oldstyle = null; + + public void saveStyle() { + if(!saved) { + oldstyle = getStyle(); + saved = true; + } + } + + public void recoverStyle() { + setStyle(oldstyle); + saved = false; + } + + + private boolean lockProperty = false, lockCell = false; + + public void lockPropertyListener(boolean lock) { + lockProperty = lock; + } + + public boolean isPropertyListenerLocked() { + return lockProperty; + } + + public boolean isCellListenerLocked() { + return lockCell; + } + + public void lockCellListener(boolean lock) { + lockCell = lock; + } + + /* + public void setCellListener(ChangeListener changeListener) { + if(this.changeListener != null) itemProperty().removeListener(this.changeListener); + this.changeListener = changeListener; + if(changeListener != null) itemProperty().addListener(changeListener); + }*/ + + public void setCellListener(InvalidationListener changeListener) { + if(this.changeListener != null) itemProperty().removeListener(this.changeListener); + this.changeListener = changeListener; + if(changeListener != null) itemProperty().addListener(changeListener); + } + + public void setPropertyListener(Property property, InvalidationListener listener) { + if(this.property != null && propertyListener != null) + this.property.removeListener(propertyListener); + + this.property = property; + this.propertyListener = listener; + if(listener != null && property != null) property.addListener(listener); + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/TransformationField.java b/src/fr/inria/structgraphics/ui/spreadsheet/TransformationField.java new file mode 100644 index 0000000000000000000000000000000000000000..4d6a2af397ce36c5b9eacad3dceb05b60dd5ccf7 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/TransformationField.java @@ -0,0 +1,144 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable.DataType; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; + +public class TransformationField extends HBox implements ChangeListener<String> { + + private DataVariable activeVariable = null; + private int activeIndex = 0; + private final static String DEFAULT_TEXT = "T(x)="; + + private ComboBox typeMenu = new ComboBox(); + private TextField textField = new TextField(); + private Label label; + + public TransformationField(HBox toolBar) { + + typeMenu.getItems().addAll("Symbolic", "Functional"); + typeMenu.setValue("Functional"); + + typeMenu.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + if(activeVariable != null && typeHasChanged(newValue)) { + // activeVariable.transformation.get().change.set(!activeVariable.transformation.get().change.get()); + // TODO: This is buggy. It should not change when clicking on a different variable!!!! + if(newValue.equals("Symbolic")) { + activeVariable.setDataType(DataType.Symbolic); + activeVariable.transformation.get().expression.addListener(expressionListener); + } else { + activeVariable.setDataType(DataType.Functional); + activeVariable.transformation.get().expression.removeListener(expressionListener); + } + + textField.setText(activeVariable.transformationProperty().get().getExpression().get()); + + if(activeVariable.collection instanceof VisCollection) ((VisCollection)activeVariable.collection).updateExtraComponents(); + } + } + }); + + textField.setOnKeyPressed(new EventHandler<KeyEvent>() + { + @Override + public void handle(KeyEvent e) + { + if (e.getCode().equals(KeyCode.ENTER)) + { + activeVariable.transformationProperty().get().setExpression(textField.getText()); + textField.setText(activeVariable.transformationProperty().get().getExpression().get()); + + label.requestFocus(); + } + } + }); + + label = new Label(DEFAULT_TEXT); + label.setStyle("-fx-padding:5px;"); + label.setAlignment(Pos.CENTER_RIGHT); + + //textField.setPadding(new Insets(10)); + this.setPadding(new Insets(10)); + + HBox.setHgrow(textField, Priority.ALWAYS); + + getChildren().add(toolBar); + getChildren().add(typeMenu); + getChildren().add(label); + getChildren().add(textField); + + disable(true); + } + + private boolean typeHasChanged(String value) { + if(value.equals("Symbolic")) { + if(activeVariable.getDataType() == DataType.Functional) return true; + else return false; + } + else { + if(activeVariable.getDataType() == DataType.Functional) return false; + else return true; + } + } + + public void disable(boolean disable) { + typeMenu.setDisable(disable); + label.setDisable(disable); + textField.setDisable(disable); + } + + public void setDefaultLabel() { + label.setText(DEFAULT_TEXT); + textField.setText(""); + } + + public void setVariable(DataVariable variable, int vindex) { + label.setText(variable.getName(vindex).get() + "="); + textField.setText(variable.transformationProperty().get().getExpression().get()); + + if(this.activeVariable == variable) return; + else if(activeVariable != null) { + // TODO: ???? + activeVariable.getName(activeIndex).removeListener(this); + activeVariable.transformation.get().expression.removeListener(expressionListener); + } + + activeVariable = variable; + activeIndex = vindex; + activeVariable.getName(vindex).addListener(this); + + if(variable.getDataType() == DataType.Symbolic) { + typeMenu.setValue("Symbolic"); + variable.transformation.get().expression.addListener(expressionListener); + } else typeMenu.setValue("Functional"); + } + + + private ChangeListener expressionListener = new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + textField.setText(activeVariable.transformationProperty().get().getExpression().get()); + } + }; + + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + label.setText(newValue + "="); + } + + + +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/VectorVariable.java b/src/fr/inria/structgraphics/ui/spreadsheet/VectorVariable.java new file mode 100644 index 0000000000000000000000000000000000000000..b75e475f3613a8120f31920eb15394a972af4e40 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/VectorVariable.java @@ -0,0 +1,62 @@ +package fr.inria.structgraphics.ui.spreadsheet; + +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.FlexibleListProperty; +import javafx.beans.binding.ListExpression; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class VectorVariable extends DataVariable { + + private Property property; + protected StringProperty name = new SimpleStringProperty(); + + public VectorVariable(VisBody collection, int row, int column, Property property) { + super(collection, row, column); + + name.set(property.getName()); + + this.property = property; + /* + if(property instanceof FlexibleListProperty) { + this.property = ((FlexibleListProperty) property).get(0); + } else this.property = property;*/ + + setTransformation(); + } + + @Override + public String getPropertyName() { + return property.getName(); + } + + @Override + public StringProperty getName(int vindex) { + return name; + } + + @Override + public int getLength() { + return 1; + } + + @Override + public int getWidth() { + if(property instanceof FlexibleListProperty) + return ((ListExpression<Property>) property).size(); + else return 1; + } + + @Override + public Property getProperty(int vindex, int hindex) { + if(property instanceof FlexibleListProperty) + return ((FlexibleListProperty)property).get(hindex); + else return property; + } + + @Override + public boolean contains(int row, int column) { + return (this.column == column && row >= this.row && row <= this.row + 1); + } +} diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/legend.png b/src/fr/inria/structgraphics/ui/spreadsheet/legend.png new file mode 100644 index 0000000000000000000000000000000000000000..c2295711ef3a2a7fc43a934df2c0269f0d5b4f76 Binary files /dev/null and b/src/fr/inria/structgraphics/ui/spreadsheet/legend.png differ diff --git a/src/fr/inria/structgraphics/ui/spreadsheet/spreadsheet.css b/src/fr/inria/structgraphics/ui/spreadsheet/spreadsheet.css new file mode 100644 index 0000000000000000000000000000000000000000..828c1e001efc88473d24da0d794ba089dd26adcc --- /dev/null +++ b/src/fr/inria/structgraphics/ui/spreadsheet/spreadsheet.css @@ -0,0 +1,162 @@ +.cell-spreadsheet .table-row-cell { + -fx-background-color: transparent; +} + +/* NORMAL CELL */ +.spreadsheet-cell:filled:selected, +.spreadsheet-cell:filled:focused:selected, +.spreadsheet-cell:filled:focused:selected:hover { + -fx-background-color: #d2dff2; + -fx-border-color: #a9a9a9; + -fx-border-width : 0.5px; + -fx-text-fill: -fx-selection-bar-text; + +} +.spreadsheet-cell:hover, +.spreadsheet-cell:filled:focused { + -fx-background-color: #DFDADA; + -fx-text-fill: -fx-text-inner-color; + -fx-background-insets: 0, 0 0 1 0; +} + +.spreadsheet-cell{ + -fx-padding: 0 0 0 0.2em; + -fx-border-color: black; + -fx-border-width : 0.3px; + -fx-background-color: -fx-table-cell-border-color,white; +} + +.tooltip { + -fx-background-radius: 0px; + -fx-background-color: + linear-gradient(#cec340, #a59c31), + linear-gradient(#fefefc, #e6dd71), + linear-gradient(#fef592, #e5d848); + -fx-background-insets: 0,1,2; + -fx-padding: 0.333333em 0.666667em 0.333333em 0.666667em; /* 4 8 4 8 */ + -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 8, 0.0 , 0 , 0 ); + -fx-text-fill:black; +} + +/* FIXED HEADERS */ +VerticalHeader > Label.fixed{ + -fx-background-color: -fx-box-border, -fx-inner-border, linear-gradient(to left, derive(-fx-color,-20%) ,derive(-fx-color,-10%)); + -fx-font-style : italic; +} + +HorizontalHeaderColumn > TableColumnHeader.column-header.table-column.fixed{ + -fx-background-color: -fx-box-border, -fx-inner-border, linear-gradient(to top, derive(-fx-color,-20%) ,derive(-fx-color,-10%)); + -fx-font-style : italic; +} + +/* HORIZONTAL AND VERTICAL HEADER SELECTION */ + +/* +VerticalHeader > Label { + -fx-background-color: -fx-box-border, -fx-inner-border, linear-gradient(to left, derive(-fx-color,-5%) ,derive(-fx-color,5%)); + -fx-background-insets: 0, 0 1 1 0, 1 2 2 1; + -fx-font-weight: bold; + -fx-size: 2em; + -fx-text-fill: -fx-selection-bar-text; + /*-fx-padding: 0.166667em;*/ + /*-fx-label-padding:0 0 0 20;*/ + -fx-alignment: center; + -fx-font-style : normal; +} */ + +VerticalHeader > Label.selected{ + -fx-background-color: -fx-box-border, -fx-inner-border, linear-gradient(to right, derive(#FFDB6D,-10%) ,derive(#FFDB6D,10%)); + -fx-background-insets: 0, 0 1 1 0, 1 2 2 1; + -fx-font-weight: bold; + -fx-size: 2em; + -fx-text-fill: -fx-selection-bar-text; + -fx-alignment: center; + -fx-border-color : #FFDB6D; +} + +/* +HorizontalHeaderColumn > TableColumnHeader.column-header.table-column.selected{ + -fx-background-color: -fx-box-border, -fx-inner-border, linear-gradient(to bottom, derive(#FFDB6D,-10%) ,derive(#FFDB6D,10%)); + -fx-background-insets: 0, 0 1 1 0, 1 2 2 1; + -fx-font-weight: bold; + -fx-size: 2em; + -fx-text-fill: -fx-selection-bar-text; + -fx-alignment: center; + -fx-border-color : #FFDB6D; +} */ + +/* HORIZONTAL HEADER VISIBILITY */ +.column-header-background.invisible { visibility: hidden; -fx-padding: -1em; } + +.cell-corner{ + -fx-background-color: red; +} + +.cell-corner.top-left{ + -fx-shape : "M 0 0 L 1 0 L 0 1 z"; +} + +.cell-corner.top-right{ + -fx-shape : "M 0 0 L -1 0 L 0 1 z"; +} + +.cell-corner.bottom-right{ + -fx-shape : "M 0 0 L -1 0 L 0 -1 z"; +} + +.cell-corner.bottom-left{ + -fx-shape : "M 0 0 L 1 0 L 0 -1 z"; +} + +.indicationLabel{ + -fx-font-style : italic; +} + +/* PICKERS */ +.picker-label{ + -fx-graphic: url("picker.png"); + -fx-background-color: white; + -fx-padding: 0 0 0 0; + -fx-alignment: center; +} + +.picker-label:hover{ + /*-fx-effect:dropshadow(gaussian, black, 10, 0.1, 0, 0);*/ + -fx-cursor:hand; +} + +/* We don't want to show the white background both for TextField +and textArea. We want it to be transparent just like Excel. + +Also we need to shift to the left the editor a bit*/ +CellView > .text-input.text-field{ + -fx-padding : 0 0 0 -0.2em; + -fx-background-color: transparent; +} +CellView > .text-input.text-area, +CellView > TextArea .scroll-pane > .viewport{ + -fx-background-color: transparent; +} + +/* I shift by 3px, it's not clean but it works for normal row (24px) as it +centers the textArea.*/ +CellView > TextArea .scroll-pane{ + -fx-padding : 3px 0 0 -0.15em; +} + +CellView > TextArea .scroll-pane > .viewport .content{ + -fx-padding : 0 0 0 0; + -fx-background-color: transparent; +} +/* The scrollBars must always have the same size because we may have +really big font in the editor (48px) and the scrollBars become obese otherwise.*/ +CellView >TextArea .scroll-bar:vertical , +CellView >TextArea .scroll-bar:horizontal { + -fx-font-size : 1em; +} + +.selection-rectangle{ + -fx-fill : transparent; + -fx-stroke : black; + -fx-stroke-width : 2; +} diff --git a/src/fr/inria/structgraphics/ui/tools/BezierDrawingTool.java b/src/fr/inria/structgraphics/ui/tools/BezierDrawingTool.java new file mode 100644 index 0000000000000000000000000000000000000000..868f60a439ca3c9361eb038a0c8cc5888671e418 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/BezierDrawingTool.java @@ -0,0 +1,23 @@ +package fr.inria.structgraphics.ui.tools; + +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; + +public class BezierDrawingTool extends DrawingTool { + + + public BezierDrawingTool(CanvasFrame canvas) { + super(canvas); + } + + @Override + protected void init() { + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("SVGPath"))); + super.init(); + } + +} + + diff --git a/src/fr/inria/structgraphics/ui/tools/CancelTranslateTransition.java b/src/fr/inria/structgraphics/ui/tools/CancelTranslateTransition.java new file mode 100644 index 0000000000000000000000000000000000000000..4e87d91f3d99f6e745f9310936851055dfbb1b3d --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/CancelTranslateTransition.java @@ -0,0 +1,35 @@ +package fr.inria.structgraphics.ui.tools; + +import javafx.animation.Transition; +import javafx.scene.shape.Line; +import javafx.util.Duration; + +public class CancelTranslateTransition extends Transition { + + private Line lineTrace; + private double x0, y0, x1, y1; + + private Tool tool; + + public CancelTranslateTransition(Duration duration, Tool tool) { + setCycleDuration(duration); + + this.lineTrace = tool.lineTrace; + x0 = lineTrace.getStartX(); + y0 = lineTrace.getStartY(); + + x1 = lineTrace.getEndX(); + y1 = lineTrace.getEndY(); + + this.tool = tool; + } + + @Override + protected void interpolate(double fraction) { + lineTrace.setEndX(fraction*x0 + (1 - fraction)*x1); + lineTrace.setEndY(fraction*y0 + (1 - fraction)*y1); + + tool.update(); + if(fraction == 1) tool.cancelComplete(); + } +} diff --git a/src/fr/inria/structgraphics/ui/tools/CollectionCreationTool.java b/src/fr/inria/structgraphics/ui/tools/CollectionCreationTool.java new file mode 100644 index 0000000000000000000000000000000000000000..5590e31fa7233f48885408b2007fb6c54823e737 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/CollectionCreationTool.java @@ -0,0 +1,110 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import fr.inria.structgraphics.ui.viscanvas.groupings.Grouping; +import fr.inria.structgraphics.ui.viscanvas.groupings.Grouping.Type; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Cursor; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +public class CollectionCreationTool extends Tool { + + private MarkSelection selectedMarks = new MarkSelection(); + + public CollectionCreationTool(CanvasFrame canvas) { + super(canvas); + + traceColor = Color.BLUE; + traceWidth = 2; + + rectangular = true; + } + + @Override + protected void init() { + cursor = Cursor.DEFAULT; + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("link"))); + //tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("BarChart"))); + + super.init(); + } + + + @Override + public void update() { + selectedMarks.archive(); + if(!cancelArea.contains(lineTrace.getEndX(), lineTrace.getEndY())) { + canvas.getVisFrame().select(selectedMarks, rectTrace, new CommonContainerSelectFilter()); + + List<Mark> toremove = new ArrayList<>(); + for(Mark mark:selectedMarks.getMarks()) { + if(mark.getContainer() instanceof VisBody) { + toremove.add(mark); + } + } + selectedMarks.getMarks().removeAll(toremove); + } + + selectedMarks.highlight(false); + } + + + @Override + public void cancelComplete() { + super.cancelComplete(); + } + + + private Mark nest(Mark mark, int targetLevel) { + int level = mark.getLevel(); + if(level < targetLevel) { + ObservableList<Mark> sublist = FXCollections.observableArrayList(); + sublist.add(mark); + return nest(new Grouping(sublist, Type.Collection).getGroup(), targetLevel); + } else return mark; + } + + + // TODO: Need to deal with nesting. What if I erase a parent node? + public void handleRelease(MouseEvent event) { + super.handleRelease(event); + + if(selectedMarks.isEmpty()) { + canvas.setActiveTool(null); + return; + } + + // Take care to apply the same level of variables to all the marks that participate in the group + int maxLevel = 0; + for(Mark mark: selectedMarks.getMarks()) { + maxLevel = Math.max(maxLevel, mark.getLevel()); + } + + ObservableList<Mark> marks = FXCollections.observableArrayList(); + + for(Mark mark: selectedMarks.getMarks()) { + marks.add(nest(mark, maxLevel)); + } + ////////////////////////////////////////////////////////////////////////////////////////////// + + Grouping grouping = new Grouping(marks, Type.Collection); + + canvas.getInspector().update(); + + selectedMarks.archive(); + selectedMarks.highlight(false); + + canvas.setActiveTool(null); + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/CommonContainerSelectFilter.java b/src/fr/inria/structgraphics/ui/tools/CommonContainerSelectFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..46c83360f2e8ded638fc4c412829f945ca3a1867 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/CommonContainerSelectFilter.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.Mark; + +public class CommonContainerSelectFilter extends SelectFilter { + + @Override + public boolean accept(ArrayList<Mark> selectedMarks, Mark mark) { + return selectedMarks.isEmpty() || selectedMarks.get(0).getContainer() == mark.getContainer(); + } +} diff --git a/src/fr/inria/structgraphics/ui/tools/DrawingTool.java b/src/fr/inria/structgraphics/ui/tools/DrawingTool.java new file mode 100644 index 0000000000000000000000000000000000000000..ed94e1e1e421023488273b519965cfa99c90ab2b --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/DrawingTool.java @@ -0,0 +1,24 @@ +package fr.inria.structgraphics.ui.tools; + +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.scene.Cursor; +import javafx.scene.paint.Color; + +public class DrawingTool extends Tool { + + protected DrawingTool() {} + + public DrawingTool(CanvasFrame canvas) { + super(canvas); + } + + @Override + protected void init() { + cursor = Cursor.CROSSHAIR; + traceColor = Color.DODGERBLUE; + traceWidth = 1; + + super.init(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/EraserTool.java b/src/fr/inria/structgraphics/ui/tools/EraserTool.java new file mode 100644 index 0000000000000000000000000000000000000000..2d2d22d47175c44f8240d84c07b5896a2ab8ceba --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/EraserTool.java @@ -0,0 +1,82 @@ +package fr.inria.structgraphics.ui.tools; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.SimpleMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.scene.Cursor; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +public class EraserTool extends Tool { + + private MarkSelection selectedMarks = new MarkSelection(); + + public EraserTool(CanvasFrame canvas) { + super(canvas); + + traceColor = Color.RED; + traceWidth = 6; + + rectangular = true; + } + + @Override + protected void init() { + cursor = Cursor.DEFAULT; + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("eraser"))); + + super.init(); + } + + + private MarkSelection selected() { + return canvas.selected(); + } + + @Override + public void handlePress(MouseEvent event) { + super.handlePress(event); + + if(!selected().isEmpty()) { + selected().archive(); + selected().getMarks().clear(); + selected().highlight(true); + } + } + + public void handleDrag(MouseEvent event) { + super.handleDrag(event); + + selectedMarks.archive(); + if(!cancelArea.contains(event.getX(), event.getY())) + canvas.getVisFrame().select(selectedMarks, rectTrace, new CommonContainerSelectFilter()); + + selectedMarks.highlight(false); + } + + + // TODO: Need to deal with nesting. What if I erase a parent node? + public void handleRelease(MouseEvent event) { + super.handleRelease(event); + + for(Mark mark: selectedMarks.getMarks()) { + mark.setHighlight(false, true); + mark.fullDelete(); + } + + canvas.getVisFrame().update(); + canvas.getInspector().update(); + canvas.getDataView().update(); + + selectedMarks.archive(); + //selectedMarks.highlight(false); + + canvas.setActiveTool(null); + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/FlowDrawingTool.java b/src/fr/inria/structgraphics/ui/tools/FlowDrawingTool.java new file mode 100644 index 0000000000000000000000000000000000000000..bb1ee60594cedf00a1d0a949ca22cefa5b46789d --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/FlowDrawingTool.java @@ -0,0 +1,111 @@ +package fr.inria.structgraphics.ui.tools; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +public class FlowDrawingTool extends DrawingTool { + + private ShapeMark origin = null, destination = null; + + public FlowDrawingTool(CanvasFrame canvas) { + super(canvas); + } + + + @Override + protected void init() { + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("flow"))); + super.init(); + + traceColor = Color.FORESTGREEN; + traceWidth = 2.5; + + } + + @Override + public void handleMove(MouseEvent event) { + ShapeMark mark = canvas.getVisFrame().getShape(event.getX(), event.getY()); + + if(mark != null && mark != origin) { + if(origin != null) origin.showOrigin(false); + mark.showOrigin(true); + origin = mark; + } else if(mark == null && origin != null){ + origin.showOrigin(false); + origin = null; + } + } + + @Override + public void handleDrag(MouseEvent event) { + if(origin != null) { + lineTrace = createTrace(event.getX(), event.getY()); + canvas.getCanvas().show(lineTrace, null, null); + + ShapeMark mark = canvas.getVisFrame().getShape(event.getX(), event.getY()); + + boolean accept = mark != null && mark != origin && origin.getRootVirtualGroup() == mark.getRootVirtualGroup(); + + if(accept) { + LineConnectedCollection collection = (LineConnectedCollection)mark.getRootVirtualGroup(); + if(collection.containtsConnection(origin, mark) || collection.hasCycle(mark, origin)) accept = false; + /*else if(collection.getChildPropertyStructure().areCommon(origin.height, mark.height)) { + accept = false; + }*/ + } + + if(accept && mark != destination) { + if(destination != null) destination.showDestination(false); + mark.showDestination(true); + destination = mark; + } else if(!accept && destination != null ) { + destination.showDestination(false); + destination = null; + } + } + } + + @Override + public void handleRelease(MouseEvent event) { + if(origin != null && destination != null) { + LineConnectedCollection collection = (LineConnectedCollection)origin.getRootVirtualGroup(); + collection.createConnection(origin, destination, true); + + //canvas.getVisFrame().update(); + canvas.getInspector().generateView(collection); + canvas.getInspector().update(); + canvas.getInspector().setView(collection); + canvas.getDataView().update(); + } + + if(destination != null) { + destination.showDestination(false); + destination = null; + } + + canvas.getCanvas().show(null, null, null); + handleMove(event); + } + + @Override + public void handlePress(MouseEvent event) { + if(origin != null) { + super.handlePress(event); + } + } + + + @Override + public void setActive(boolean active) { + super.setActive(active); + } + +} + + diff --git a/src/fr/inria/structgraphics/ui/tools/GroupCreationTool.java b/src/fr/inria/structgraphics/ui/tools/GroupCreationTool.java new file mode 100644 index 0000000000000000000000000000000000000000..c5768a249c695032007db4da3123111f0c694018 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/GroupCreationTool.java @@ -0,0 +1,110 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import fr.inria.structgraphics.ui.viscanvas.groupings.Grouping; +import fr.inria.structgraphics.ui.viscanvas.groupings.Grouping.Type; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Cursor; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +public class GroupCreationTool extends Tool { + + private MarkSelection selectedMarks = new MarkSelection(); + + public GroupCreationTool(CanvasFrame canvas) { + super(canvas); + + traceColor = Color.BLUE; + traceWidth = 2; + + rectangular = true; + } + + @Override + protected void init() { + cursor = Cursor.DEFAULT; + //tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("construct"))); + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("Group"))); + + super.init(); + } + + + @Override + public void update() { + selectedMarks.archive(); + if(!cancelArea.contains(lineTrace.getEndX(), lineTrace.getEndY())) { + canvas.getVisFrame().select(selectedMarks, rectTrace, new CommonContainerSelectFilter()); + + List<Mark> toremove = new ArrayList<>(); + for(Mark mark:selectedMarks.getMarks()) { + if(mark.getContainer() instanceof VisBody) { + toremove.add(mark); + } + } + selectedMarks.getMarks().removeAll(toremove); + } + + selectedMarks.highlight(false); + } + + + @Override + public void cancelComplete() { + super.cancelComplete(); + } + + + private Mark nest(Mark mark, int targetLevel) { + int level = mark.getLevel(); + if(level < targetLevel) { + ObservableList<Mark> sublist = FXCollections.observableArrayList(); + sublist.add(mark); + return nest(new Grouping(sublist, Type.Group).getGroup(), targetLevel); + } else return mark; + } + + + // TODO: Need to deal with nesting. What if I erase a parent node? + public void handleRelease(MouseEvent event) { + super.handleRelease(event); + + if(selectedMarks.isEmpty()) { + canvas.setActiveTool(null); + return; + } + + // Take care to apply the same level of variables to all the marks that participate in the group + int maxLevel = 0; + for(Mark mark: selectedMarks.getMarks()) { + maxLevel = Math.max(maxLevel, mark.getLevel()); + } + + ObservableList<Mark> marks = FXCollections.observableArrayList(); + + for(Mark mark: selectedMarks.getMarks()) { + marks.add(nest(mark, maxLevel)); + } + ////////////////////////////////////////////////////////////////////////////////////////////// + + Grouping grouping = new Grouping(marks, Type.Group); + + canvas.getInspector().update(); + + selectedMarks.archive(); + selectedMarks.highlight(false); + + canvas.setActiveTool(null); + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/MarkSelection.java b/src/fr/inria/structgraphics/ui/tools/MarkSelection.java new file mode 100644 index 0000000000000000000000000000000000000000..1eb4321bcf66741e6340350065563b76b0ee538f --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/MarkSelection.java @@ -0,0 +1,168 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import javafx.scene.shape.Line; + +public class MarkSelection { + + public enum SelectionType { + INCOLLECTION, INGROUP, UNGROUPED + } + + private ArrayList<Mark> marks, archived; + private boolean acceptDragEvent = true; // TODO: May finally do not need it!!!! + + private SelectionType type = SelectionType.UNGROUPED; + + public MarkSelection () { + marks = new ArrayList<>(); + } + + public void archive() { + archived = marks; + marks = new ArrayList<>(); + } + + public boolean isEmpty() { + return marks.isEmpty(); + } + + public int size() { + return marks.size(); + } + + public void removeAll() { + marks.clear(); + } + + public void highlightPress() { + if(marks.size() != 1 || !archived.contains(marks.get(0))) { + highlight(true); + } + else { + marks = archived; + for(Mark mark:marks) { + mark.pin(); + } + } + } + + + public void highlight(boolean single) { + if(archived != null) { + for(Mark mark:archived) { + if(!marks.contains(mark)) { + mark.setHighlight(false, single); + } + } + } + + if(marks != null) { + for(Mark mark:marks) { + /*if(!mark.isHighlighted())*/ mark.setHighlight(true, single); + } + } + } + + public void add(Mark mark) { + Container container = mark.getContainer(); + + if(container instanceof VisCollection) type = SelectionType.INCOLLECTION; + else if(container instanceof VisGroup) type = SelectionType.INGROUP; + else type = SelectionType.UNGROUPED; + + marks.add(mark); + } + + public SelectionType getType() { + return type; + } + + + private boolean isOrderConstrained() { + if(marks == null || marks.isEmpty()) return false; + Container container = marks.get(0).getContainer(); + + if(container instanceof VisBody) { + return ((VisBody) container).isOrderConstrained(); + } else return false; + } + + + public boolean canChangeLayers() { + if(marks == null || marks.isEmpty()) return false; + else { + Mark first = marks.get(0); + if(first instanceof LineConnectedCollection && ((LineConnectedCollection) first).isConnectionSelected()) return false; + } + + return !isOrderConstrained(); + } + + public boolean canGoToLibrary() { + if(marks == null || marks.isEmpty() || size() > 1) return false; + else { + Mark first = marks.get(0); + if(first instanceof LineConnectedCollection && ((LineConnectedCollection) first).isConnectionSelected()) return false; + } + + return true; + } + + + public ArrayList<Mark> getMarks(){ + return marks; + } + + public void translate(Line lineTrace) { + for(Mark mark:marks) { + mark.translate(lineTrace); + } + } + + public void setDragAccept(boolean accept) { + this.acceptDragEvent = accept; + } + + /* + public boolean acceptDragEvent() { + return acceptDragEvent; + }*/ + + + public void bringToFront() { + Container container = marks.get(0).getContainer(); + + for(Mark mark: marks) { + container.bringToFront(mark); + } + + container.updateReordering(); + } + + public void sendToBack() { + Container container = marks.get(0).getContainer(); + + for(int i = marks.size() - 1; i >=0; --i) { + container.sendToBack(marks.get(i)); + } + + container.updateReordering(); + } + + public void delete() { + for(Mark mark:marks) { + mark.fullDelete(); + } + + marks.clear(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/MultiplyTool.java b/src/fr/inria/structgraphics/ui/tools/MultiplyTool.java new file mode 100644 index 0000000000000000000000000000000000000000..b3e921593fdbd75118c1217540e7e3f01e75ff92 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/MultiplyTool.java @@ -0,0 +1,214 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.List; + +import fr.inria.structgraphics.graphics.ConnectedCollection; +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.Shadow; +import fr.inria.structgraphics.graphics.SimpleMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.AlignmentProperty.XSticky; +import fr.inria.structgraphics.types.AlignmentProperty.YSticky; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import fr.inria.structgraphics.ui.viscanvas.groupings.PropertyStructure; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Cursor; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.shape.Circle; + +public class MultiplyTool extends Tool { + + protected Circle trace; + protected double traceRadius = 5; + +// private boolean pressed = false; + + private SimpleMark selected = null; + + public MultiplyTool(CanvasFrame canvas) { + super(canvas); + + trace = createPointTrace(-1, -1); + trace.setVisible(false); + } + + @Override + protected void init() { + cursor = Cursor.DEFAULT; + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("multiply"))); + + super.init(); + } + + @Override + public void handleMove(MouseEvent event) { + trace.centerXProperty().set(event.getX()); + trace.centerYProperty().set(event.getY()); + } + + @Override + public void handlePress(MouseEvent event) { + super.handlePress(event); + canvas.getCanvas().show(trace); + + if(!selected().isEmpty()) { + selected().archive(); + canvas.getVisFrame().monoselect(selected(), null); + selected().highlight(true); + } + + selected().archive(); + + canvas.getVisFrame().monoselect(selected(), trace); + selected().highlight(false); + + if(!selected().isEmpty()) { + // pressed = true; + canvas.getInspector().setView(selected().getMarks()); + + Mark mark = selected().getMarks().get(0); + if(mark instanceof SimpleMark) { + selected = (SimpleMark)mark; + selected.setFeedForward(true); + } + + }// else pressed = false; + } + + @Override + public void handleDrag(MouseEvent event) { + super.handleDrag(event); + + if(selected != null) { + selected.selectCopies(lineTrace); + } + } + + + private MarkSelection selected() { + return canvas.selected(); + } + + + // TODO: Need to deal with nesting. What if I erase a parent node? + @Override + public void handleRelease(MouseEvent event) { + if(selected != null) { + List<Shadow> copies = selected.selectCopies(lineTrace); + if(copies.isEmpty()) { + selected.setFeedForward(false); + selected = null; + super.handleRelease(event); + return; + } + + if(selected instanceof ConnectedCollection && ((ConnectedCollection)selected).isCurveSelected() && ((ConnectedCollection)selected).isSingleSibling()) { + ObservableList<Mark> charts = FXCollections.observableArrayList(); + charts.add(selected); + + for(Shadow shadow: copies) { + SimpleMark mark = shadow.createMark(); + if(mark == null) continue; // TODO : ???? + + mark.initProperties(); + charts.add(mark); + } + + Container container = selected.getContainer(); + LineConnectedCollection collection = new LineConnectedCollection(container, true, charts); + collection.getAlignXProperty().set(YSticky.Yes); + collection.getAlignYProperty().set(XSticky.Yes); + + collection.getChildPropertyStructure().moveToVariable(new PropertyName(selected.strokePaint.getName())); + + // TODO + if(container instanceof VisBody) { + ((VisBody)container).stickToYAxis(); + ((VisBody)container).stickToXAxis(); + } + + for(Mark mark: collection.getComponents()) + if(mark instanceof ConnectedCollection) ((ConnectedCollection)mark).updateInteractor(); + + } else { + Container container = selected.getContainer(); + Mark firstMark = null; + PropertyStructure structure = null; + + if(container instanceof VisBody && container.getComponents().size() == 1) { + firstMark = container.getComponentAt(0); + structure = ((VisBody)container).getChildPropertyStructure(); + } + + for(Shadow shadow: copies) { + SimpleMark mark = shadow.createMark(); + if(mark == null) continue; // TODO : ???? + + mark.initProperties(); + if(container instanceof VisBody) { + if(firstMark != null) { + if(mark.coords.getX() != firstMark.coords.getX() && structure.isXShared()) { + structure.moveToVariable(new PropertyName(firstMark.coords.x.getName())); + } else if(mark.coords.getY() != firstMark.coords.getY() && structure.isYShared()){ + structure.moveToVariable(new PropertyName(firstMark.coords.y.getName())); + } + } + // TODO: I need something extra for marks that are groups themselves + ((VisBody)container).addToGroup(mark); + } + + } + if(selected.getContainer() instanceof VisBody) { + ((VisBody)selected.getContainer()).stickToYAxis(); + ((VisBody)selected.getContainer()).stickToXAxis(); + + ((VisBody)selected.getContainer()).updateLayout(((VisBody)selected.getContainer()).getComponentAt(0)); + } + } + + canvas.getVisFrame().update(); + canvas.getInspector().update(); + canvas.getDataView().update(); + + selected.setFeedForward(false); + selected = null; + } + +// pressed = false; + + super.handleRelease(event); + + selected().archive(); + canvas.getVisFrame().monoselect(selected(), null); + selected().highlight(false); + + canvas.setActiveTool(null); +// for(Mark mark: selectedMarks.getMarks()) { + // Linking code +// mark.dispose(); +// } + + /* + canvas.getInspector().update(); + + selectedMarks.archive(); + */ + } + + protected Circle createPointTrace(double x, double y) { + Circle trace = new Circle(x, y, traceRadius); + trace.setStrokeWidth(1); + trace.setFill(null); + trace.setStroke(traceColor); + + return trace; + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/SelectFilter.java b/src/fr/inria/structgraphics/ui/tools/SelectFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..930e8e0f739ff53a91d366173c6108cef22dec9b --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/SelectFilter.java @@ -0,0 +1,12 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.Mark; + +public class SelectFilter { + + public boolean accept(ArrayList<Mark> selectedMarks, Mark mark) { + return true; + } +} diff --git a/src/fr/inria/structgraphics/ui/tools/SelectTool.java b/src/fr/inria/structgraphics/ui/tools/SelectTool.java new file mode 100644 index 0000000000000000000000000000000000000000..9d011047792bc3fff9ec1aee8673ca15eb1433ff --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/SelectTool.java @@ -0,0 +1,332 @@ +package fr.inria.structgraphics.ui.tools; + +import java.util.ArrayList; +import java.util.Collection; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.SimpleMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.persistence.JsonObjectWriter; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.spreadsheet.AreaInteractor; +import fr.inria.structgraphics.ui.tools.MarkSelection.SelectionType; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Cursor; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; +import javafx.stage.WindowEvent; + +public class SelectTool extends Tool { + + protected Circle trace; + protected double traceRadius = 2; + + private boolean pressed = false; + + private ContextMenu contextMenu; + + private ArrayList<SimpleMark> clipboard = new ArrayList<>(); + + public SelectTool(CanvasFrame canvas) { + super(canvas); + + trace = createPointTrace(-1, -1); + trace.setVisible(false); + + rectangular = true; + } + + @Override + protected void init() { + cursor = Cursor.DEFAULT; + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon("drag"))); + + super.init(); + } + + @Override + public void handleMove(MouseEvent event) { + super.handleMove(event); + trace.centerXProperty().set(event.getX()); + trace.centerYProperty().set(event.getY()); + } + + public void select(Mark mark, Object source) { + select(mark, source, true); + } + + public void select(Mark mark, Object source, boolean single) { + selected().setDragAccept(true); + selected().archive(); + if(mark != null) selected().add(mark); + selected().highlight(single); + + if(!selected().isEmpty()) { + pressed = true; + if(source instanceof AreaInteractor) canvas.getInspector().setView(selected().getMarks()); + } else pressed = false; + } + + public void addSelection(Mark mark, Object source) { + if(mark != null) selected().add(mark); + selected().highlight(false); + } + + @Override + public void handlePress(MouseEvent event) { + super.handlePress(event); + + canvas.getCanvas().show(trace); + + selected().setDragAccept(true); + selected().archive(); + canvas.getVisFrame().monoselect(selected(), trace); + + //selected().highlight(true); + selected().highlightPress(); + + if(!selected().isEmpty()) { + rectangular = false; + + pressed = true; + canvas.getInspector().setView(selected().getMarks()); + } else { + //canvas.getInspector().setView(canvas.getVisFrame()); + rectangular = true; + pressed = false; + } + + if(event.isSecondaryButtonDown() && !selected().isEmpty() && selected().getMarks().get(0) instanceof SimpleMark ) { + if(contextMenu != null) { + contextMenu.hide(); + contextMenu.getItems().clear(); + } + else { + contextMenu = new ContextMenu(); + } + + addItems(contextMenu); + canvas.getSplitPane().setContextMenu(contextMenu); + } + } + + + public void addItems(ContextMenu contextMenu) { + // Move Layer Position Menu Items + MenuItem item1 = new MenuItem("Bring to Front"); + item1.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + selected().bringToFront(); + } + }); + + contextMenu.getItems().add(item1); + //if(selected().isOrderConstrained()) item1.setDisable(true); + + MenuItem item2 = new MenuItem("Send to Back"); + item2.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + selected().sendToBack(); + } + }); + contextMenu.getItems().add(item2); + //if(selected().isOrderConstrained()) item2.setDisable(true); + + + contextMenu.getItems().add(new SeparatorMenuItem()); + + // Remove marks! + MenuItem item3 = new MenuItem("Delete"); + item3.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + selected().delete(); + + canvas.getInspector().update(); + canvas.getDataView().update(); + } + }); + contextMenu.getItems().add(item3); + + MenuItem item4 = new MenuItem("Add to Library"); + item4.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + canvas.getLibrary().addToLibrary((SimpleMark)selected().getMarks().get(0)); + } + }); + contextMenu.getItems().add(item4); + + + MenuItem item5 = new MenuItem("Save As"); + item5.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + canvas.saveAs(canvas.getStage(), + JsonObjectWriter.saveToJason(selected().getMarks(), false)); + } + }); + contextMenu.getItems().add(item5); + + + contextMenu.setOnShowing(new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent event) { + boolean activeLayers = selected().canChangeLayers(); + + item1.setDisable(!activeLayers); + item2.setDisable(!activeLayers); + item4.setDisable(!selected().canGoToLibrary()); + } + + }); + + contextMenu.setOnHiding(new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent event) { + canvas.getSplitPane().setContextMenu(null); + } + }); + } + + @Override + public void update() { + if(!pressed) { + selected().archive(); + if(!cancelArea.contains(lineTrace.getEndX(), lineTrace.getEndY())) + canvas.getVisFrame().select(selected(), /*lineTrace*/ rectTrace, new CommonContainerSelectFilter()); + + selected().highlight(false); + + //if(selected().getMarks().size() > 1) selected().highlight(false); + //else selected().highlight(true); + } else { + //if(selected().acceptDragEvent()) + selected().translate(lineTrace); + //canvas.getInspector().update(); + } + } + + private MarkSelection selected() { + return canvas.selected(); + } + + public Collection<Mark> getSelected(){ + return canvas.selected().getMarks(); + } + + // TODO: Need to deal with nesting. What if I erase a parent node? + @Override + public void handleRelease(MouseEvent event) { + if(!pressed){ + if(selected().getMarks().size() > 1) { + selected().highlight(false); + } + else selected().highlight(true); + + canvas.getInspector().setView(selected().getMarks()); + } + + pressed = false; + + super.handleRelease(event); + + for(Mark mark: selected().getMarks()) { + mark.mouseRelease(); + // Update the coordinates of the marks in the virtual group enclosing the released mark!!! + if(mark.getContainer() instanceof VisBody) { + //((VirtualGroup)mark.getContainer()).lockProperties(); + //((VirtualGroup)mark.getContainer()).refresh(); + //((VirtualGroup)mark.getContainer()).unlockProperties(); + } + // Linking code +// mark.dispose(); + } + } + + + @Override + public void setActive(boolean active) { + super.setActive(active); + if(active) { + selected().highlight(true); + } else { + selected().archive(); + selected().highlight(true); + } + } + + @Override + public void cancelComplete() { + super.cancelComplete(); + pressed = false; + } + + + protected Rectangle createRectSelection(double x, double y, double width, double height) { + Rectangle rectTrace = new Rectangle(x, y, width, height); + rectTrace.setStrokeWidth(rectWidth); + rectTrace.getStrokeDashArray().addAll(3.0,7.0,3.0,7.0); + rectTrace.setStroke(rectColor); + rectTrace.setFill(rectFill); + + return rectTrace; + } + + + protected Circle createPointTrace(double x, double y) { + Circle trace = new Circle(x, y, traceRadius); + trace.setStrokeWidth(1); + trace.setFill(null); + trace.setStroke(traceColor); + + return trace; + } + + + protected void copyAction() { + if(selected().isEmpty()) return; + else { + clipboard.clear(); + } + + for(Mark mark: selected().getMarks()) { + if(mark instanceof SimpleMark) clipboard.add((SimpleMark)mark); + } + } + + protected void pasteAction(double x, double y) { + if(clipboard.isEmpty()) return; + + double meanX = 0, meanY = 0; + for(Mark mark: clipboard) { + meanX += mark.getGlobalX(); + meanY += mark.getGlobalY(); + } + + meanX = meanX / clipboard.size(); + meanY = meanY / clipboard.size(); + + for(SimpleMark mark: clipboard) { + SimpleMark copy = mark.createCopy(canvas.getVisFrame(), 0, 0); + copy.initProperties(); + copy.getCoords().x.set(x + mark.getGlobalX() - meanX); + copy.getCoords().y.set((canvas.getVisFrame().height.get() - y) + mark.getGlobalY() - meanY); + copy.update(); + } + + canvas.getInspector().update(); + + } + +} diff --git a/src/fr/inria/structgraphics/ui/tools/ShapeDrawingTool.java b/src/fr/inria/structgraphics/ui/tools/ShapeDrawingTool.java new file mode 100644 index 0000000000000000000000000000000000000000..26775040d8539f8b15e12d51ff58f945826847ed --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/ShapeDrawingTool.java @@ -0,0 +1,134 @@ +package fr.inria.structgraphics.ui.tools; + +import fr.inria.structgraphics.graphics.PositionCoords; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.TextualMark; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.types.ShapeProperty.Type; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.utils.CoordTransformer; +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.scene.Cursor; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +public class ShapeDrawingTool extends DrawingTool { + + protected ShapeMark tempMark; + private ShapeProperty.Type type; + + public ShapeDrawingTool(CanvasFrame canvas, ShapeProperty.Type type) { + this.canvas = canvas; + this.type = type; + init(); + } + + @Override + protected void init() { + cursor = Cursor.CROSSHAIR; + tbutton = new ToggleButton("", new ImageView(DisplayUtils.getIcon(type.toString()))); + super.init(); + } + + public void update() { + if(tempMark == null) { + if(!cancelArea.contains(lineTrace.getEndX(), lineTrace.getEndY())) { + tempMark = createMark(); + canvas.getInspector().update(); + canvas.getInspector().setView(tempMark); + } + } else { + if(!cancelArea.contains(lineTrace.getEndX(), lineTrace.getEndX())) updateMark(); + else { + tempMark.dispose(); // TODO: ???? + + canvas.getInspector().update(); + tempMark = null; + } + } + } + + @Override + public void cancelComplete() { + super.cancelComplete(); + tempMark.dispose(); + canvas.getInspector().update(); + tempMark = null; + } + + + public void handleRelease(MouseEvent event) { + super.handleRelease(event); + + // tempMark.setHighlight(true, true); + tempMark = null; + + // canvas.setActiveTool(null); + + // TODO + } + + + protected ShapeMark createMark() { + VisFrame visFrame = canvas.getVisFrame(); // This will eventually become more sophisticated! + + RefX refX = RefX.Center; + RefY refY = RefY.Bottom; + + double w = (type == Type.Line) ? lineTrace.getEndX() - lineTrace.getStartX() : Math.abs((lineTrace.getEndX() - lineTrace.getStartX())); + double h = (type == Type.Line) ? lineTrace.getEndY() - lineTrace.getStartY() : Math.abs((lineTrace.getEndY() - lineTrace.getStartY())); + + double x = (type == Type.Line) ? + CoordTransformer.getXPosition(visFrame.getRefCoords(), refX, visFrame.width.get(), lineTrace.getStartX(), w) : + CoordTransformer.getXPosition(visFrame.getRefCoords(), refX, visFrame.width.get(), Math.min(lineTrace.getStartX(), lineTrace.getEndX()), w); + + double y = (type == Type.Line) ? + CoordTransformer.getYPosition(visFrame.getRefCoords(), refY, visFrame.height.get(), -lineTrace.getStartY(), h) : + CoordTransformer.getYPosition(visFrame.getRefCoords(), refY, visFrame.height.get(), -Math.min(lineTrace.getStartY(), lineTrace.getEndY()), h); + + + ShapeMark mark = ShapeMark.createInstance(visFrame, true, type, w, h); + + PositionCoords coords = new PositionCoords(mark, x, y); + mark.setCoords(coords); + + // TODO + mark.setDefaults(); + + mark.initProperties(); + mark.updateShape(); + + return mark; + } + + + protected void updateMark() { + if(tempMark!= null) { + + VisFrame visFrame = (VisFrame)tempMark.getContainer(); + + double w = (type == Type.Line) ? lineTrace.getEndX() - lineTrace.getStartX() : Math.abs((lineTrace.getEndX() - lineTrace.getStartX())); + double h = (type == Type.Line) ? lineTrace.getEndY() - lineTrace.getStartY() : Math.abs((lineTrace.getEndY() - lineTrace.getStartY())); + + double x = (type == Type.Line) ? + CoordTransformer.getXPosition(visFrame.getRefCoords(), tempMark.getCoords().getXRef(), visFrame.width.get(), lineTrace.getStartX(), w) : + CoordTransformer.getXPosition(visFrame.getRefCoords(), tempMark.getCoords().getXRef(), visFrame.width.get(), Math.min(lineTrace.getStartX(), lineTrace.getEndX()), w); + + double y = (type == Type.Line) ? + CoordTransformer.getYPosition(visFrame.getRefCoords(), tempMark.getCoords().getYRef(), visFrame.height.get(), -lineTrace.getStartY(), h) : + CoordTransformer.getYPosition(visFrame.getRefCoords(), tempMark.getCoords().getYRef(), visFrame.height.get(), -Math.min(lineTrace.getStartY(), lineTrace.getEndY()), h); + + tempMark.setPositionCoords(x, y); + + tempMark.width.set(w); + tempMark.height.set(h); + } + } + +} + diff --git a/src/fr/inria/structgraphics/ui/tools/Tool.java b/src/fr/inria/structgraphics/ui/tools/Tool.java new file mode 100644 index 0000000000000000000000000000000000000000..d104cad524422fbea3c6e0150a37028fb4fe31c5 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/tools/Tool.java @@ -0,0 +1,176 @@ +package fr.inria.structgraphics.ui.tools; + +import fr.inria.structgraphics.ui.viscanvas.CanvasFrame; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Cursor; +import javafx.scene.control.ToggleButton; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; +import javafx.util.Duration; + +public class Tool { + + private final static int CANCEL_RADIUS = 0; + + protected double startX, startY, mouseX, mouseY; + protected Line lineTrace; + protected Rectangle rectTrace; + protected Shape cancelArea; + + protected ToggleButton tbutton = null; + protected boolean isActive = false; + protected Cursor cursor = Cursor.DEFAULT; + protected CanvasFrame canvas; + + protected double traceWidth = 3; + protected Paint traceColor = Color.CORAL; + + protected double rectWidth = 1; + protected Paint rectColor = Color.DARKGRAY; + protected Paint rectFill = new Color(1, 1, 1, .1); + + protected boolean canceled = false; + protected boolean rectangular = false; + + protected Tool() {} + + public Tool(CanvasFrame canvas) { + this.canvas = canvas; + init(); + } + + protected void init() { + if(tbutton != null) tbutton.setOnAction( + new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + if(!isActive) setActive(true); + else tbutton.setSelected(true); + } + }); + } + + public boolean hasToggleButton() { + return true; + } + + protected void cancelAction() { + canceled = true; + CancelTranslateTransition transition = new CancelTranslateTransition(Duration.millis(160), this); + transition.play(); + } + + protected void copyAction() {} + protected void pasteAction(double x, double y) {} + + public void cancelComplete() { + canvas.getCanvas().show(null, null, null); + } + + public ToggleButton getToggleButton() { + return tbutton; + } + + public void setActive(boolean active) { + this.isActive = active; + if(active) { + if(tbutton != null) tbutton.setSelected(true); + canvas.setActiveTool(this); + if(cursor!= null) canvas.setCanvasCursor(cursor); + + canvas.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent event) { + if(event.getCode() == KeyCode.ESCAPE) + cancelAction(); + else if(event.isMetaDown() && event.getCode() == KeyCode.C) + copyAction(); + else if(event.isMetaDown() && event.getCode() == KeyCode.V) + pasteAction(mouseX, mouseY); + } + }); + + } else if(tbutton != null) tbutton.setSelected(false); + } + + public boolean getActive() { + return isActive; + } + + public void handleDrag(MouseEvent event) { + if(canceled) return; + + lineTrace = createTrace(event.getX(), event.getY()); + cancelArea = createCancelArea(event.getX(), event.getY()); + if(rectangular) { + rectTrace = createRectTrace(event.getX(), event.getY()); + } else rectTrace = null; + + canvas.getCanvas().show(lineTrace, cancelArea, rectTrace); + update(); + } + + public void update() { + + } + + public void handleMove(MouseEvent event) { + mouseX = event.getX(); + mouseY = event.getY(); + } + + public void handlePress(MouseEvent event) { + canceled = false; + + startX = event.getX(); + startY = event.getY(); + lineTrace = createTrace(event.getX(), event.getY()); + + cancelArea = createCancelArea(event.getX(), event.getY()); + canvas.getCanvas().show(null, cancelArea, null); + } + + public void handleRelease(MouseEvent event) { + canvas.getCanvas().show(null, null, null); + } + + // This is a default implementation if the canel area, but it could be overriden + protected Shape createCancelArea(double x, double y) { + Shape cancelArea = new Circle(startX, startY, CANCEL_RADIUS); + cancelArea.setStrokeWidth(1); + cancelArea.setStroke(Color.RED); + cancelArea.setFill(Color.TRANSPARENT); + + return cancelArea; + } + + protected Line createTrace(double x, double y) { + Line lineTrace = new Line(startX, startY, x, y); + lineTrace.setStrokeWidth(traceWidth); + lineTrace.setStroke(traceColor); + + return lineTrace; + } + + protected Rectangle createRectTrace(double x, double y) { + Rectangle rectTrace = new Rectangle(Math.min(startX, x), Math.min(startY, y), Math.abs(x - startX), Math.abs(y - startY)); + rectTrace.setStrokeWidth(rectWidth); + rectTrace.getStrokeDashArray().addAll(3.0,7.0,3.0,7.0); + rectTrace.setStroke(rectColor); + rectTrace.setFill(rectFill); + + return rectTrace; + } + + public void doubleClicked(MouseEvent event) {} + +} + diff --git a/src/fr/inria/structgraphics/ui/utils/BidirectionalBindings.java b/src/fr/inria/structgraphics/ui/utils/BidirectionalBindings.java new file mode 100644 index 0000000000000000000000000000000000000000..520f448d41529285d0be44ab1b0a2a8eb185528b --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/BidirectionalBindings.java @@ -0,0 +1,46 @@ +package fr.inria.structgraphics.ui.utils; + +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.util.Pair; + +// See: http://carl-witt.de/customized-bidirectional-bindings-in-javafx/ + +public class BidirectionalBindings { + /** Executes updateB when propertyA is changed. Executes updateA when propertyB is changed. + * Makes sure that no update loops are caused by mutual updates. + * @return + */ + public static <A,B> Pair<ChangeListener<A>, ChangeListener<B>> bindBidirectional(Property<A> propertyA, Property<B> propertyB, + ChangeListener<A> updateB, ChangeListener<B> updateA){ + ChangeListener<A> listenerA = addFlaggedChangeListener(propertyA, updateB); + ChangeListener<B> listenerB = addFlaggedChangeListener(propertyB, updateA); + + return new Pair<ChangeListener<A>, ChangeListener<B>>(listenerA, listenerB); + } + + public static <A,B> void unbindBidirectional(Property<A> property1, Property<B> property2, Pair<ChangeListener<A>, ChangeListener<B>> pair) { + property1.removeListener(pair.getKey()); + property2.removeListener(pair.getValue()); + } + + private static <T> ChangeListener<T> addFlaggedChangeListener(Property<T> property, ChangeListener<T> updateProperty){ + ChangeListener<T> listener = new ChangeListener<T>() { + private boolean alreadyCalled = false; + + @Override public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) { + if(alreadyCalled) return; + try { + alreadyCalled = true; + updateProperty.changed(observable,oldValue,newValue); + } + finally { alreadyCalled = false; } + } + }; + + property.addListener(listener); + + return listener; + } +} diff --git a/src/fr/inria/structgraphics/ui/utils/Cloner.java b/src/fr/inria/structgraphics/ui/utils/Cloner.java new file mode 100644 index 0000000000000000000000000000000000000000..299e53cd1c314deb87ad56f92164aed4bd3922a0 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/Cloner.java @@ -0,0 +1,92 @@ +package fr.inria.structgraphics.ui.utils; + +import javafx.scene.shape.Circle; +import javafx.scene.shape.CubicCurveTo; +import javafx.scene.shape.Line; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.scene.shape.QuadCurveTo; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; + +public class Cloner { + + public static Shape clone(Shape shape) { + + if(shape instanceof Rectangle){ + Rectangle clone = new Rectangle(((Rectangle) shape).getX(), ((Rectangle) shape).getY(), ((Rectangle) shape).getWidth(), ((Rectangle) shape).getHeight()); + + /* + clone.xProperty().bind(((Rectangle) shape).xProperty()); + clone.yProperty().bind(((Rectangle) shape).yProperty()); + clone.widthProperty().bind(((Rectangle) shape).widthProperty()); + clone.heightProperty().bind(((Rectangle) shape).heightProperty()); + */ + + return clone; + } else if(shape instanceof Circle){ + Circle clone = new Circle(((Circle) shape).getCenterX(), ((Circle) shape).getCenterY(), ((Circle) shape).getRadius()); + + /* + clone.centerXProperty().bind(((Circle) shape).centerXProperty()); + clone.centerYProperty().bind(((Circle) shape).centerYProperty()); + clone.radiusProperty().bind(((Circle) shape).radiusProperty()); + */ + + return clone; + } else if(shape instanceof Line){ + Line clone = new Line(((Line) shape).getStartX(), ((Line) shape).getStartY(), ((Line) shape).getEndX(), ((Line) shape).getEndY()); + + /* + clone.startXProperty().bind(((Line) shape).startXProperty()); + clone.startYProperty().bind(((Line) shape).startYProperty()); + clone.endXProperty().bind(((Line) shape).endXProperty()); + clone.endYProperty().bind(((Line) shape).endYProperty()); + */ + + return clone; + } else if(shape instanceof Path){ + Path clone = new Path(((Path) shape).getElements()); + + /* + Path clone = new Path(); + for(PathElement e: ((Path) shape).getElements()) { + if(e instanceof MoveTo) { + MoveTo mt = new MoveTo(); + mt.xProperty().bind(((MoveTo)e).xProperty()); + mt.yProperty().bind(((MoveTo)e).yProperty()); + clone.getElements().add(mt); + } else if(e instanceof LineTo) { + LineTo lt = new LineTo(); + lt.xProperty().bind(((LineTo)e).xProperty()); + lt.yProperty().bind(((LineTo)e).yProperty()); + clone.getElements().add(lt); + } else if(e instanceof QuadCurveTo) { + QuadCurveTo qt = new QuadCurveTo(); + qt.xProperty().bind(((QuadCurveTo)e).xProperty()); + qt.yProperty().bind(((QuadCurveTo)e).yProperty()); + qt.controlXProperty().bind(((QuadCurveTo)e).controlXProperty()); + qt.controlYProperty().bind(((QuadCurveTo)e).controlYProperty()); + clone.getElements().add(qt); + } else if(e instanceof CubicCurveTo) { + CubicCurveTo ct = new CubicCurveTo(); + ct.xProperty().bind(((CubicCurveTo)e).xProperty()); + ct.yProperty().bind(((CubicCurveTo)e).yProperty()); + ct.controlX1Property().bind(((CubicCurveTo)e).controlX1Property()); + ct.controlY1Property().bind(((CubicCurveTo)e).controlY1Property()); + ct.controlX2Property().bind(((CubicCurveTo)e).controlX2Property()); + ct.controlY2Property().bind(((CubicCurveTo)e).controlY2Property()); + clone.getElements().add(ct); + } + }*/ + + return clone; + } + + // TODO: ... + return null; + } + +} diff --git a/src/fr/inria/structgraphics/ui/utils/CoordTransformer.java b/src/fr/inria/structgraphics/ui/utils/CoordTransformer.java new file mode 100644 index 0000000000000000000000000000000000000000..ead5643d5f536fa8fa29e3dbb795a6c4e769dad0 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/CoordTransformer.java @@ -0,0 +1,42 @@ +package fr.inria.structgraphics.ui.utils; + +import fr.inria.structgraphics.graphics.ReferenceCoords; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; + +public class CoordTransformer { + + public static double getXPosition(ReferenceCoords refCoords, RefX ref, double containerWidth, double x, double w) { + RefX containerRef = refCoords.containerXRef.get(); + double offset = 0; + if(containerRef == RefX.Center) offset = containerWidth / 2; + else if(containerRef == RefX.Right) offset = containerWidth; + + if(ref == RefX.Left) { + return x - offset; + } + else if(ref == RefX.Center) { + return x + w/2 - offset; + } + else { + return x + w - offset; + } + } + + public static double getYPosition(ReferenceCoords refCoords, RefY ref, double containerHeight, double y, double h) { + RefY containerRef = refCoords.containerYRef.get(); + double offset = 0; + if(containerRef == RefY.Center) offset = containerHeight / 2; + else if(containerRef == RefY.Bottom) offset = containerHeight; + + if(ref == RefY.Top) { + return y + offset; + } + else if(ref == RefY.Center) { + return y - h/2 + offset; + } + else { + return y - h + offset; + } + } +} diff --git a/src/fr/inria/structgraphics/ui/utils/FlowConnectionBinding.java b/src/fr/inria/structgraphics/ui/utils/FlowConnectionBinding.java new file mode 100644 index 0000000000000000000000000000000000000000..947c7eb62f491d7ac69b3b97bdb62ee68a2341bc --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/FlowConnectionBinding.java @@ -0,0 +1,318 @@ +package fr.inria.structgraphics.ui.utils; + +import java.util.ArrayList; +import java.util.Hashtable; + +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.ui.viscanvas.groupings.FlowConnection; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +/* + * This binding mechanism ensures that updates of flow marks heights and flow sizes are correctly propagates without loops + */ + +public class FlowConnectionBinding { + + private ShapeMark mark; + private ArrayList<FlowConnection> inwardConnections, outwardConnections; + + // Original Listeners + private LockedChangeListener backwardsListener, forwardsListener; + private Hashtable<FlowConnection, LockedChangeListener> fromBackListeners, fromFrontListeners; + + private boolean lock = false; + + public FlowConnectionBinding(ShapeMark mark) { + this.mark = mark; + inwardConnections = new ArrayList<FlowConnection>(); + outwardConnections = new ArrayList<FlowConnection>(); + + fromBackListeners = new Hashtable<>(); + fromFrontListeners = new Hashtable<>(); + + backwardsListener = new LockedChangeListener() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + if(alreadyCalled || lock) return; + try { + for(LockedChangeListener listener: fromBackListeners.values()) { + listener.alreadyCalled = true; + } + + updateInwardConnections(oldValue.doubleValue(), newValue.doubleValue()); + + } + finally { + for(LockedChangeListener listener: fromBackListeners.values()) { + listener.alreadyCalled = false; + } + } + } + }; + + forwardsListener = new LockedChangeListener() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + if(alreadyCalled || lock) return; + try { + for(LockedChangeListener listener: fromFrontListeners.values()) { + listener.alreadyCalled = true; + } + + updateOutwardConnections(oldValue.doubleValue(), newValue.doubleValue()); + + } + finally { + for(LockedChangeListener listener: fromFrontListeners.values()) { + listener.alreadyCalled = false; + } + } + } + }; + + mark.height.addListener(backwardsListener); + mark.height.addListener(forwardsListener); + } + + public ArrayList<FlowConnection> getInwardConnections(){ + return inwardConnections; + } + + public ArrayList<FlowConnection> getOutwardConnections(){ + return outwardConnections; + } + + private void updateOutwardConnections(double oldvalue, double newvalue) { + if(outwardConnections.isEmpty() || newvalue > oldvalue) return; + + double sum = 0; + for(FlowConnection conn:outwardConnections) { + sum += conn.weight.get(); + } + + if(sum <= mark.height.get()) return; + + double sum_ = 0; + for(int i = 0; i < outwardConnections.size() - 1; ++i) { + FlowConnection conn = outwardConnections.get(i); + double ratio = conn.weight.doubleValue()/sum; + double w = abs(ratio*newvalue); + conn.weight.set(w); + sum_ += w; + } + + FlowConnection conn = outwardConnections.get(outwardConnections.size() - 1); + conn.weight.set(abs(newvalue - sum_)); + } + + private void updateInwardConnections(double oldvalue, double newvalue) { + if(inwardConnections.isEmpty() || newvalue > oldvalue) return; + + double sum = 0; + for(FlowConnection conn:inwardConnections) { + sum += conn.weight.get(); + } + + if(sum <= mark.height.get()) return; + + // TODO: Find a more precise calculation method... + double ratio = newvalue / oldvalue; + + sum = 0; + for(int i = 0; i < inwardConnections.size() - 1; ++i) { + FlowConnection conn = inwardConnections.get(i); + double w = abs(ratio*conn.weight.doubleValue()); + conn.weight.set(w); + sum += w; + } + + FlowConnection conn = inwardConnections.get(inwardConnections.size() - 1); + conn.weight.set(abs(newvalue - sum)); + } + + + private void updateOutwardConnections2(double oldvalue, double newvalue) { + if(outwardConnections.isEmpty()) return; + + double sum = 0; + for(FlowConnection conn:outwardConnections) { + sum += conn.weight.get(); + } + +// TODO: To uncomment to allow for inpomplete outbound flows +// if(newvalue > sum) return; + + double sum_ = 0; + for(int i = 0; i < outwardConnections.size() - 1; ++i) { + FlowConnection conn = outwardConnections.get(i); + double ratio = conn.weight.doubleValue()/sum; + double w = abs(ratio*newvalue); + conn.weight.set(w); + sum_ += w; + } + + FlowConnection conn = outwardConnections.get(outwardConnections.size() - 1); + conn.weight.set(abs(newvalue - sum_)); + } + + private void updateInwardConnections2(double oldvalue, double newvalue) { + if(inwardConnections.isEmpty()) return; + + // TODO: Find a more precise calculation method... + double ratio = newvalue / oldvalue; + + double sum = 0; + for(int i = 0; i < inwardConnections.size() - 1; ++i) { + FlowConnection conn = inwardConnections.get(i); + double w = abs(ratio*conn.weight.doubleValue()); + conn.weight.set(w); + sum += w; + } + + FlowConnection conn = inwardConnections.get(inwardConnections.size() - 1); + conn.weight.set(abs(newvalue - sum)); + } + + + private void updateFromFront2(FlowConnection destination, double oldvalue, double newvalue) { + double w = 0; + for(FlowConnection connection: outwardConnections) { + w += connection.weight.get(); + } + + // TODO: To uncomment to allow for inpomplete outbound flows + //if(mark.height() < w) + mark.height.set(w); + } + + private void updateFromBack2(FlowConnection origin, double oldvalue, double newvalue) { + double w = 0; + for(FlowConnection connection: inwardConnections) { + w += connection.weight.get(); + } + + mark.height.set(w); + + //mark.height.set(mark.height() - oldvalue + newvalue); + } + + + private void updateFromFront(FlowConnection destination, double oldvalue, double newvalue) { + updateHeight(); + } + + private void updateFromBack(FlowConnection origin, double oldvalue, double newvalue) { + updateHeight(); + } + + private void updateHeight() { + double w1 = 0; + for(FlowConnection connection: outwardConnections) { + w1 += connection.weight.get(); + } + + double w2 = 0; + for(FlowConnection connection: inwardConnections) { + w2 += connection.weight.get(); + } + + mark.height.set(Math.max(w1, w2)); + } + + private double abs(double w) { + return Math.abs(w); + } + + public void addInwardConnection(FlowConnection connection) { + inwardConnections.add(connection); + + LockedChangeListener listener = new LockedChangeListener() { + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + if(alreadyCalled || lock) return; + try { + backwardsListener.alreadyCalled = true; + updateFromBack(connection, oldValue.doubleValue(), newValue.doubleValue()); + } + finally { + backwardsListener.alreadyCalled = false; + } + } + }; + + fromBackListeners.put(connection, listener); + connection.weight.addListener(listener); + } + + public void addOutwardConnection(FlowConnection connection) { + outwardConnections.add(connection); + + LockedChangeListener listener = new LockedChangeListener(){ + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + if(alreadyCalled || lock) return; + try { + forwardsListener.alreadyCalled = true; + updateFromFront(connection, oldValue.doubleValue(), newValue.doubleValue()); + } + finally { + forwardsListener.alreadyCalled = false; + } + } + }; + + fromFrontListeners.put(connection, listener); + connection.weight.addListener(listener); + } + + public void removeInwardConnection(FlowConnection connection) { + inwardConnections.remove(connection); + + LockedChangeListener listener = fromFrontListeners.get(connection); + if(listener != null) connection.weight.removeListener(listener); + fromFrontListeners.remove(connection); + } + + public void removeOutwardConnection(FlowConnection connection) { + outwardConnections.remove(connection); + + LockedChangeListener listener = fromBackListeners.get(connection); + if(listener != null) connection.weight.removeListener(listener); + fromBackListeners.remove(connection); + } + + + public void destroy() { + for(FlowConnection connection: fromFrontListeners.keySet()) { + connection.weight.removeListener(fromFrontListeners.get(connection)); + connection.destroy(); + connection.detachFront(); + } + + for(FlowConnection connection: fromBackListeners.keySet()) { + connection.weight.removeListener(fromBackListeners.get(connection)); + connection.destroy(); + connection.detachBack(); + } + + mark.height.removeListener(backwardsListener); + mark.height.removeListener(forwardsListener); + + fromFrontListeners.clear(); + fromBackListeners.clear(); + inwardConnections.clear(); + outwardConnections.clear(); + } + + class LockedChangeListener implements ChangeListener<Number> { + protected boolean alreadyCalled = false; + + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {} + } + + public void lockListeners(boolean lock) { + this.lock = lock; + } +} diff --git a/src/fr/inria/structgraphics/ui/utils/GroupBinding.java b/src/fr/inria/structgraphics/ui/utils/GroupBinding.java new file mode 100644 index 0000000000000000000000000000000000000000..1ee68edffe7c923b0e7892547fd4af232daf76bc --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/GroupBinding.java @@ -0,0 +1,179 @@ +package fr.inria.structgraphics.ui.utils; + +import java.util.ArrayList; + +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.Shareable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +// See: http://carl-witt.de/customized-bidirectional-bindings-in-javafx/ + +public class GroupBinding<A> { + + private ArrayList<ChangeListener<A>> originListeners = null; + private ArrayList<LockedChangeListener> listeners = new ArrayList<>(); + private ArrayList<Property<A>> properties; + + private GroupBinding<Boolean> visibilityBindings = null; + + + public GroupBinding(ArrayList<Property<A>> properties, boolean withVisibility) { + this.properties = properties; + + if(withVisibility) { + visibilityBindings = createVisibilityBindings(); + rebind(); + } + } + + public GroupBinding(ArrayList<Property<A>> properties) { + this(properties, true); + } + + public GroupBinding(ArrayList<Property<A>> properties, ArrayList<ChangeListener<A>> listeners) { + this.properties = properties; + this.originListeners = listeners; + visibilityBindings = createVisibilityBindings(); + + rebind(); + } + + private GroupBinding<Boolean> createVisibilityBindings() { + ArrayList<BooleanProperty> visProperties = new ArrayList<>(); + for(Property prop: properties) + visProperties.add(((Shareable)prop).getPublicProperty()); + + return new GroupBinding(visProperties, false); + } + + public boolean hasValue(Property<A> property) { + return (!properties.isEmpty() && properties.get(0).getValue().equals(property.getValue())); + } + + public boolean contains(Property<A> property) { + return properties.contains(property); + } + + public ArrayList<Property<A>> getProperties(){ + return properties; + } + + public void unbind() { + if(visibilityBindings != null) visibilityBindings.unbind(); + + for(int i = 0; i<properties.size(); ++i) { + properties.get(i).removeListener(listeners.get(i)); + } + + listeners.clear(); + } + + public void rebind() { + if(originListeners == null) { + originListeners = new ArrayList<>(); + + for(Property<A> prop: properties) { + originListeners.add(new ChangeListener<A>() { + @Override + public void changed(ObservableValue<? extends A> observable, A oldValue, A newValue) { + for(Property<A> prop_: properties) { + if(prop != prop_) prop_.setValue(newValue); + } + } + }); + } + } + + listeners = addFlaggedChangeListeners(); + + if(visibilityBindings != null) visibilityBindings.rebind(); + } + + public Property<A> firstProperty(){ + if(isEmpty()) return null; + else return properties.get(0); + } + + public void resetProperties(ArrayList<Property<A>> properties_) { + unbind(); + properties.clear(); + properties.addAll(properties_); + originListeners.clear(); + originListeners = null; + rebind(); + + if(visibilityBindings != null) { + ArrayList<Property<Boolean>> pubPRoperties = new ArrayList<>(); + for(Property<A> prop: properties_) + pubPRoperties.add(((Shareable)prop).getPublicProperty()); + + visibilityBindings.resetProperties(pubPRoperties); + } + } + + public void add(Property<A> property) { + unbind(); + properties.add(property); + rebind(); + + if(visibilityBindings != null) visibilityBindings.add(((Shareable)property).getPublicProperty()); + } + + public void remove(Property<A> property) { + unbind(); + properties.remove(property); + rebind(); + + if(visibilityBindings != null) visibilityBindings.remove(((Shareable)property).getPublicProperty()); + } + + public boolean isEmpty() { + return properties.isEmpty(); + } + + protected ArrayList<LockedChangeListener> addFlaggedChangeListeners(){ + ArrayList<LockedChangeListener> newlisteners = new ArrayList<>(); + + int i = 0; + for(Property<A> prop: properties) { + ChangeListener<A> updateProperty = originListeners.get(i++); + LockedChangeListener listener = new LockedChangeListener(updateProperty); + newlisteners.add(listener); + + prop.addListener(listener); + } + + return newlisteners; + } + + + class LockedChangeListener implements ChangeListener<A> { + protected boolean alreadyCalled = false; + protected ChangeListener<A> updateProperty = null; + + LockedChangeListener(ChangeListener<A> updateProperty){ + this.updateProperty = updateProperty; + } + + @Override + public void changed(ObservableValue<? extends A> observable, A oldValue, A newValue) { + if(alreadyCalled) return; + try { + for(GroupBinding<A>.LockedChangeListener listener: listeners) { + listener.alreadyCalled = true; + } + // alreadyCalled = true; + updateProperty.changed(observable, oldValue, newValue); + } + finally { + for(GroupBinding<A>.LockedChangeListener listener: listeners) { + listener.alreadyCalled = false; + } + } + } + } + +} diff --git a/src/fr/inria/structgraphics/ui/utils/PropertyCloner.java b/src/fr/inria/structgraphics/ui/utils/PropertyCloner.java new file mode 100644 index 0000000000000000000000000000000000000000..0466a1d23ebc4096d190a6412462ffc80d6b6df5 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/PropertyCloner.java @@ -0,0 +1,7 @@ +package fr.inria.structgraphics.ui.utils; + +import javafx.beans.property.Property; + +public class PropertyCloner { + +} diff --git a/src/fr/inria/structgraphics/ui/utils/SortedMarkSet.java b/src/fr/inria/structgraphics/ui/utils/SortedMarkSet.java new file mode 100644 index 0000000000000000000000000000000000000000..bac68b0952c25d5dc455b02b345e69a8e37a2c2b --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/SortedMarkSet.java @@ -0,0 +1,24 @@ +package fr.inria.structgraphics.ui.utils; + +import java.util.Comparator; +import java.util.TreeSet; + +import fr.inria.structgraphics.graphics.Mark; + +public class SortedMarkSet extends TreeSet<Mark> { + + public SortedMarkSet(boolean xaxis) { + super(new Comparator<Mark>() { + @Override + public int compare(Mark mark1, Mark mark2) { + if(xaxis) { + if(mark1.getCoords().getX() <= mark2.getCoords().getX()) return -1; + else return 1; + } else { + if(mark1.getCoords().getY() <= mark2.getCoords().getY()) return -1; + else return 1; + } + } + }); + } +} diff --git a/src/fr/inria/structgraphics/ui/utils/Stats.java b/src/fr/inria/structgraphics/ui/utils/Stats.java new file mode 100644 index 0000000000000000000000000000000000000000..d7a11480a28cdf33607976b4b97cf0dd700d9ed7 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/Stats.java @@ -0,0 +1,27 @@ +package fr.inria.structgraphics.ui.utils; + +import java.util.List; + +public class Stats { + public double min = Double.MAX_VALUE, max = Double.MIN_VALUE, mean = 0, var = 0; + + public Stats(List<Double> numbers){ + double sum = 0; + + for(double num: numbers) { + sum += num; + min = Math.min(num, min); + max = Math.max(num, max); + } + + mean = sum / numbers.size(); + + sum = 0; + for(double num: numbers) { + sum += Math.pow(num - mean, 2); + } + + var = sum / numbers.size(); + } + +} \ No newline at end of file diff --git a/src/fr/inria/structgraphics/ui/utils/Ticker.java b/src/fr/inria/structgraphics/ui/utils/Ticker.java new file mode 100644 index 0000000000000000000000000000000000000000..cf330c64e9ad30e657c39a35193bb5cb70d5456d --- /dev/null +++ b/src/fr/inria/structgraphics/ui/utils/Ticker.java @@ -0,0 +1,152 @@ +package fr.inria.structgraphics.ui.utils; + +import java.util.ArrayList; +import java.util.List; + +// Author: http://erison.blogspot.com/2011/07/algorithm-for-optimal-scaling-on-chart.html + +public class Ticker { + + private double minPoint; + private double maxPoint; + private double maxTicks; + private double tickSpacing; + private double range; + private double niceMin; + private double niceMax; + + + public Ticker(double min, double max) { + this(min, max, 10, 20); + } + + /** + * Instantiates a new instance of the NiceScale class. + * + * @param min the minimum data point on the axis + * @param max the maximum data point on the axis + * @param maxTicks the maximum number of ticks on the axis + * @param minGap the minimum gap between two ticks + */ + public Ticker(double min, double max, double maxTicks, double minGap) { + this.maxTicks = Math.min(maxTicks, (max - min)/minGap); + this.minPoint = min; + this.maxPoint = max; + calculate(); + } + + /** + * Calculate and update values for tick spacing and nice + * minimum and maximum data points on the axis. + */ + private void calculate() { + this.range = niceNum(maxPoint - minPoint, false); + this.tickSpacing = niceNum(range / (maxTicks - 1), true); + this.niceMin = + Math.floor(minPoint / tickSpacing) * tickSpacing; + this.niceMax = + Math.ceil(maxPoint / tickSpacing) * tickSpacing; + } + + /** + * Returns a "nice" number approximately equal to range Rounds + * the number if round = true Takes the ceiling if round = false. + * + * @param range the data range + * @param round whether to round the result + * @return a "nice" number to be used for the data range + */ + private double niceNum(double range, boolean round) { + double exponent; /** exponent of range */ + double fraction; /** fractional part of range */ + double niceFraction; /** nice, rounded fraction */ + + exponent = Math.floor(Math.log10(range)); + fraction = range / Math.pow(10, exponent); + + if (round) { + if (fraction < 1.5) + niceFraction = 1; + else if (fraction < 3) + niceFraction = 2; + else if (fraction < 7) + niceFraction = 5; + else + niceFraction = 10; + } else { + if (fraction <= 1) + niceFraction = 1; + else if (fraction <= 2) + niceFraction = 2; + else if (fraction <= 5) + niceFraction = 5; + else + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); + } + + /** + * Sets the minimum and maximum data points for the axis. + * + * @param minPoint the minimum data point on the axis + * @param maxPoint the maximum data point on the axis + */ + public void setMinMaxPoints(double minPoint, double maxPoint) { + this.minPoint = minPoint; + this.maxPoint = maxPoint; + calculate(); + } + + /** + * Sets maximum number of tick marks we're comfortable with + * + * @param maxTicks the maximum number of tick marks for the axis + */ + public void setMaxTicks(double maxTicks) { + this.maxTicks = maxTicks; + calculate(); + } + + /** + * Gets the tick spacing. + * + * @return the tick spacing + */ + public double getTickSpacing() { + return tickSpacing; + } + + /** + * Gets the "nice" minimum data point. + * + * @return the new minimum data point for the axis scale + */ + public double getNiceMin() { + return niceMin; + } + + /** + * Gets the "nice" maximum data point. + * + * @return the new maximum data point for the axis scale + */ + public double getNiceMax() { + return niceMax; + } + + public List<Double> getTicks(){ + double step = getTickSpacing(); + double min = getNiceMin(); + double max = getNiceMax(); + + List<Double> ticks = new ArrayList<>(); + + for(double f = min; f <= max; f+=step) + /*if(f >= minPoint)*/ ticks.add(f); + + return ticks; + + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/Canvas.java b/src/fr/inria/structgraphics/ui/viscanvas/Canvas.java new file mode 100644 index 0000000000000000000000000000000000000000..fcd94df01a83e2a440066f5011931bddd0be5c56 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/Canvas.java @@ -0,0 +1,49 @@ +package fr.inria.structgraphics.ui.viscanvas; + +import javafx.geometry.Pos; +import javafx.scene.Group; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; + +public class Canvas extends StackPane { + + private Group feedbackLayer; + private Rectangle rect; + + public Canvas() { + alignmentProperty().set(Pos.TOP_LEFT); + feedbackLayer = new Group(); + } + + public void setContent(Group group) { + getChildren().clear(); + getChildren().add(group); + + getChildren().add(feedbackLayer); + rect = new Rectangle(getWidth(), getHeight()); + rect.setStroke(null); + rect.setFill(Color.TRANSPARENT); + } + + public void show(Shape shape, Shape cancelArea, Shape rectTrace) { + feedbackLayer.getChildren().clear(); + if(cancelArea != null || shape != null || rectTrace != null) { + feedbackLayer.getChildren().add(rect); + + if(rectTrace != null) feedbackLayer.getChildren().add(rectTrace); + if(cancelArea != null) feedbackLayer.getChildren().add(cancelArea); + if(shape != null) feedbackLayer.getChildren().add(shape); + } + } + + public void show(Shape shape) { + feedbackLayer.getChildren().add(shape); + } + + public void hide(Shape shape) { + feedbackLayer.getChildren().remove(shape); + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/CanvasFrame.java b/src/fr/inria/structgraphics/ui/viscanvas/CanvasFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..465445b8e508d3372fabb369dfd963b629814adc --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/CanvasFrame.java @@ -0,0 +1,500 @@ +package fr.inria.structgraphics.ui.viscanvas; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.Optional; + +import javax.json.JsonArray; +import javax.json.JsonObject; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.MarkFactory; +import fr.inria.structgraphics.graphics.SimpleMark; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.persistence.JsonDescription; +import fr.inria.structgraphics.persistence.JsonObjectReader; +import fr.inria.structgraphics.persistence.JsonObjectWriter; +import fr.inria.structgraphics.persistence.JsonSpreadsheetWriterReader; +import fr.inria.structgraphics.persistence.JsonWorkspaceWriter; +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.ui.DisplayPreferences; +import fr.inria.structgraphics.ui.inspector.DisplayUtils; +import fr.inria.structgraphics.ui.inspector.InspectorView; +import fr.inria.structgraphics.ui.library.LibraryPane; +import fr.inria.structgraphics.ui.spreadsheet.DataView; +import fr.inria.structgraphics.ui.tools.CollectionCreationTool; +import fr.inria.structgraphics.ui.tools.EraserTool; +import fr.inria.structgraphics.ui.tools.FlowDrawingTool; +import fr.inria.structgraphics.ui.tools.GroupCreationTool; +import fr.inria.structgraphics.ui.tools.MarkSelection; +import fr.inria.structgraphics.ui.tools.MultiplyTool; +import fr.inria.structgraphics.ui.tools.SelectTool; +import fr.inria.structgraphics.ui.tools.ShapeDrawingTool; +import fr.inria.structgraphics.ui.tools.Tool; +import javafx.animation.Transition; +import javafx.beans.binding.Bindings; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Cursor; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Control; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Separator; +import javafx.scene.control.SplitPane; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import javafx.util.Duration; +import sun.security.x509.CRLReasonCodeExtension; + +public class CanvasFrame extends BorderPane { + + public static final String STYLESHEETS = CanvasFrame.class.getResource("canvasframe.css").toExternalForm(); + + public final static int LIB_WIDTH = 150; + + private Cursor visCursor = Cursor.DEFAULT; + + VBox menus; + + private SplitPane splitPane; + private BorderPane canvasPane; + private LibraryPane libraryPane; + + private Button clean, open, saveAs; + private Tool select, line, rect, circle, triangle, flowline, eraser, link, construct, multiply, text; + private Tool selectedTool; + private ToggleButton showAnchors, showLibrary; + + private Canvas canvas; + private VisFrame visframe; + + private InspectorView inspector; + private DataView dataview; + + private SimpleMark draggable; + + private MarkSelection selectedMarks = new MarkSelection(); + private Stage stage; + + public CanvasFrame(Stage stage) { + canvas = new Canvas(); + this.stage = stage; + + menus = new VBox(); + setTop(menus); + + initTools(); + setDrawingListeners(); + + canvasPane = new BorderPane(); + + libraryPane = new LibraryPane(this); + libraryPane.setMinWidth(LIB_WIDTH); + ScrollPane scroller = new ScrollPane(); + scroller.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scroller.setContent(libraryPane.getCanvas()); + scroller.setStyle("-fx-background: #FFFFFF;"); + + libraryPane.getCanvas().minWidthProperty().bind(Bindings.createDoubleBinding(() -> + scroller.getViewportBounds().getWidth(), scroller.viewportBoundsProperty())); + libraryPane.setCenter(scroller); + + splitPane = new SplitPane(); + splitPane.setDividerPosition(0, 0); + splitPane.getItems().addAll(libraryPane, canvasPane); + + SplitPane.setResizableWithParent(libraryPane, false); + + splitPane.setId("splitpane"); + + setCenter(splitPane); + + libraryPane.load(); + } + + public Stage getStage() { + return stage; + } + + private void initTools() { + select = new SelectTool(this); + line = new ShapeDrawingTool(this, ShapeProperty.Type.Line); + rect = new ShapeDrawingTool(this, ShapeProperty.Type.Rectangle); + circle = new ShapeDrawingTool(this, ShapeProperty.Type.Ellipse); + triangle = new ShapeDrawingTool(this, ShapeProperty.Type.Triangle); + flowline = new FlowDrawingTool(this); + text = new ShapeDrawingTool(this, ShapeProperty.Type.Text); + eraser = new EraserTool(this); + link = new CollectionCreationTool(this); + multiply = new MultiplyTool(this); + + construct = new GroupCreationTool(this); + + setActiveTool(select); + select.setActive(true); + } + + public LibraryPane getLibrary() { + return libraryPane; + } + + public MarkSelection selected() { + return selectedMarks; + } + + public void setVisFrame(VisFrame frame) { + visframe = frame; + canvas.setContent(frame.getGroup()); + } + + private void setDrawingListeners() { + canvas.setOnMouseMoved(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + //if(select.getActive()) return; + selectedTool.handleMove(event); + } + }); + + canvas.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + //if(select.getActive()) return; + selectedTool.handlePress(event); + } + }); + + canvas.setOnMouseDragged(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + //if(select.getActive()) return; + selectedTool.handleDrag(event); + } + }); + + canvas.setOnMouseReleased(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + //if(select.getActive()) return; + selectedTool.handleRelease(event); + } + }); + } + + + public VisFrame getVisFrame() { + return visframe; + } + + public void setViews(InspectorView inspector, DataView dataview) { + this.inspector = inspector; + this.dataview = dataview; + + dataview.setSelector((SelectTool)select); + inspector.setSelector((SelectTool)select); + } + + public InspectorView getInspector() { + return inspector; + } + + public DataView getDataView() { + return dataview; + } + + public void setMenubars(Stage stage, Stage inspectStage) { + + stage.getScene().cursorProperty().bindBidirectional(inspectStage.getScene().cursorProperty()); + stage.getScene().cursorProperty().bindBidirectional(canvas.cursorProperty()); + + canvas.setOnMouseEntered(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + stage.getScene().cursorProperty().set(visCursor); + //setCursor(visCursor); /// There is still a bug here, when i reactivate the window!!!! + //getCenter().setCursor(visCursor); + } + }); + + canvas.setOnMouseExited(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + stage.getScene().cursorProperty().set(Cursor.DEFAULT); + //getCenter().setCursor(Cursor.DEFAULT); + } + }); + + + // VisMenuBar menuBar = new VisMenuBar(stage, inspectStage, this, inspector, dataview); + // menus.getChildren().add(menuBar); + + open = new Button("Open", new ImageView(DisplayUtils.getIcon("open-file"))); + clean = new Button("Clean", new ImageView(DisplayUtils.getIcon("cleaning"))); + saveAs = new Button("Save", new ImageView(DisplayUtils.getIcon("save"))); + + showAnchors = new ToggleButton(null, new ImageView(DisplayUtils.getIcon("axis"))); + showAnchors.setTooltip(new Tooltip("Show/Hide Anchors")); + + showLibrary = new ToggleButton(null, new ImageView(DisplayUtils.getIcon("library"))); + showLibrary.setTooltip(new Tooltip("Library")); + + select.getToggleButton().setTooltip(new Tooltip("Select")); + multiply.getToggleButton().setTooltip(new Tooltip("Replicate")); + link.getToggleButton().setTooltip(new Tooltip("Create Collection")); + construct.getToggleButton().setTooltip(new Tooltip("Create Group")); + eraser.getToggleButton().setTooltip(new Tooltip("Erase")); + + line.getToggleButton().setTooltip(new Tooltip("Draw Line")); + rect.getToggleButton().setTooltip(new Tooltip("Draw Rectangle")); + triangle.getToggleButton().setTooltip(new Tooltip("Draw Triangle")); + text.getToggleButton().setTooltip(new Tooltip("Create Textbox")); + flowline.getToggleButton().setTooltip(new Tooltip("Draw a Connection")); + + // From: https://www.flaticon.com/free-icon/cleaning_1215980 + + select.setActive(true); + + ToolBar toolBar = new ToolBar(open, saveAs, clean, new Separator(), select.getToggleButton(), multiply.getToggleButton(), link.getToggleButton(), construct.getToggleButton(), eraser.getToggleButton(), + new Separator(), + /* groupshape.getToggleButton(),*/ + line.getToggleButton(), + rect.getToggleButton(), circle.getToggleButton(), triangle.getToggleButton(), text.getToggleButton(), flowline.getToggleButton(), + new Separator(), showAnchors, showLibrary); + menus.getChildren().add(toolBar); + + // Add Listeners + open.setOnAction(new EventHandler<ActionEvent>() { + public void handle(ActionEvent t) { + openSyntax(stage); + } + }); + + saveAs.setOnAction(new EventHandler<ActionEvent>() { + public void handle(ActionEvent t) { + saveWorkspace(stage); + } + }); + + + clean.setOnAction( + new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent e) { + Alert alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Confirmation Dialog"); + alert.setHeaderText(null); + alert.setContentText("Are you sure you want to clean the canvas?"); + + Optional<ButtonType> result = alert.showAndWait(); + if (result.get() == ButtonType.OK){ + cleanCanvas(); + } else { + // ... user chose CANCEL or closed the dialog + } + } + }); + + showAnchors.setSelected(true); + showAnchors.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + visframe.showGroupInteractors(showAnchors.isSelected()); + } + }); + + showLibrary.setSelected(true); + showLibrary.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + showLibrary(showLibrary.isSelected()); + } + }); + } + + private void showLibrary(boolean show) { + if(show) { + Transition transition = new Transition() { + { + setCycleDuration(Duration.millis(150)); + } + + @Override + protected void interpolate(double frac) { + libraryPane.setMinWidth(LIB_WIDTH*frac); + splitPane.setDividerPosition(0, 0); + + if(frac == 1) libraryPane.setMaxWidth(-1); + } + + }; + transition.play(); + } + else { + Transition transition = new Transition() { + { + setCycleDuration(Duration.millis(150)); + } + + @Override + protected void interpolate(double frac) { + double w = LIB_WIDTH*(1 - frac); + libraryPane.setMinWidth(w); + splitPane.setDividerPosition(0, 0); + + if(frac == 1) libraryPane.setMaxWidth(w); + } + + }; + transition.play(); + + } + } + + public Canvas getCanvas() { + return canvas; + } + + public void setCanvasCursor(Cursor cursor) { + visCursor = cursor; + } + + + public void setActiveTool(Tool tool) { + if(tool == null) tool = select; + + if(selectedTool != tool) { + if(selectedTool != null) selectedTool.setActive(false); + selectedTool = tool; + if(!selectedTool.getActive()) selectedTool.setActive(true); + } + } + + private void openSyntax(Stage visStage) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setInitialDirectory( + new File("./json") + ); + + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter("Json", "*.json", "*.wrk") + ); + + fileChooser.setTitle("Open File (json or wrk)"); + File file = fileChooser.showOpenDialog(visStage); + if(file == null) return; + + JsonDescription descr = new JsonDescription(file); + if(descr.isWorkspace()) { + libraryPane.clear(); + cleanCanvas(); + libraryPane.load(descr.getLibraryArray(true)); + JsonObjectReader.readChildrenFromJasonArray(visframe, descr.getVisArray(true)); + inspector.update(); + + // TODO: Add the spreadsheet + JsonSpreadsheetWriterReader.readSpreadsheetFromJasonObject(descr.getSpreadsheetArray(), visframe, dataview.getSpreadsheet()); + } + else if(descr.isLibrary()) { + libraryPane.clear(); + libraryPane.load(descr.getLibraryArray(false)); + } + else { + JsonArray visArray = descr.getVisArray(false); + JsonObjectReader.readChildrenFromJasonArray(visframe, visArray); + inspector.update(); + } + } + + public void saveAs(Stage visStage, JsonObject visualizations) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setInitialDirectory(new File("./json")); + + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter("Json", "*.json") + ); + + fileChooser.setTitle("Save As Json"); + File file = fileChooser.showSaveDialog(visStage); + if(file == null) return; + else { + try { + FileWriter writer = new FileWriter(file); + writer.write(JsonObjectWriter.fromObjectToReadableString(visualizations, 0).toString()); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void saveWorkspace(Stage visStage) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setInitialDirectory(new File("./sessions")); + + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Workspace", "*.wrk")); + fileChooser.setTitle("Save Workspace (Json)"); + File file = fileChooser.showSaveDialog(visStage); + if(file == null) return; + else { + try { + FileWriter writer = new FileWriter(file); + JsonObject dataObject = JsonWorkspaceWriter.saveToJason(libraryPane.getComponents(), visframe.getComponents(), dataview.getSpreadsheet()); + + writer.write(JsonObjectWriter.fromObjectToReadableString(dataObject, 0).toString()); + + //writer.write(dataObject.toString()); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + + private void cleanCanvas() { + VisFrame visFrame = MarkFactory.createVisFrame(DisplayPreferences.CANVAS_WIDTH, DisplayPreferences.CANVAS_HEIGHT); + visFrame.initProperties(); + + // ScrollPane scroller = (ScrollPane)getCenter(); + // scroller.setContent(new StackPane(visFrame.getGroup())); + + setVisFrame(visFrame); + + inspector.setVisualizationFrame(visFrame); + inspector.update(); + + dataview.reset(); + } + + public void setDraggable(SimpleMark mark) { + draggable = mark; + } + + public SimpleMark getDragged() { + return draggable; + } + + public void setSketchArea(ScrollPane scroller) { + canvasPane.setCenter(scroller); + } + + public Control getSplitPane() { + return splitPane; + } + + public void close() { + libraryPane.save(); + } +} + diff --git a/src/fr/inria/structgraphics/ui/viscanvas/canvasframe.css b/src/fr/inria/structgraphics/ui/viscanvas/canvasframe.css new file mode 100644 index 0000000000000000000000000000000000000000..af2b4fdd0685e5abb2ee46c722abf6e0e937bfeb --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/canvasframe.css @@ -0,0 +1,34 @@ +/* + * Scenic View, + * Copyright (C) 2012 Jonathan Giles, Ander Ruiz, Amy Fowler + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +.split-pane { + -fx-background-insets: 0, 1 0 1 0; + -fx-padding: 0 0 1 0; +} +.split-pane *.split-pane-divider{ + -fx-padding: 0 1 0 0; + -fx-background-color: transparent, -fx-box-border; + -fx-background-insets: 0 -3 0 -3, 0; + -fx-border-color: null; +} +.split-pane *.horizontal-grabber { + -fx-padding: 0; + -fx-shape: ""; +} + diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/CollectionPropertyStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/CollectionPropertyStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..8c91d8901a00ff93df1ed105d52572793376ad8d --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/CollectionPropertyStructure.java @@ -0,0 +1,441 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.ConstrainedDoubleProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.NestedListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.ui.inspector.util.PropertyCloner; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import javafx.beans.property.Property; + +public class CollectionPropertyStructure extends PropertyStructure { + + protected TreeMap<PropertyName, Property> shared; + protected List<Property> merged; + + protected TreeMap<PropertyName, DimensionalBinding> bindings; + protected TreeMap<PropertyName, GroupBinding> publicBindings; + + public CollectionPropertyStructure(Collection<Mark> marks) { + this(marks, new DefaultSharingStrategy()); + } + + public CollectionPropertyStructure(Collection<Mark> marks, CollectionPropertyStructure structure) { + properties = new TreeMap<>(); + + for(Mark mark: marks) { + mark.addSelfProperties(true, properties); + } + + for(FlexibleListProperty property:properties.values()) + { + if(property.getName().matches(PropertyName.getRegExpr("x")) ) { + id_x = new PropertyName(property.getName()); + if(id_y != null) break; + } + else if(property.getName().matches(PropertyName.getRegExpr("y")) ) { + id_y = new PropertyName(property.getName()); + if(id_x != null) break; + } + } + + common = new TreeSet<>(); + common.addAll(structure.common); + + variable = new TreeSet<>(); + variable.addAll(structure.variable); + + shared = new TreeMap<>(); + bindings = new TreeMap<>(); + + merge(); + + bindGroupPublic(marks); + } + + + public CollectionPropertyStructure(Collection<Mark> marks, SortedSet<PropertyName> common, SortedSet<PropertyName> variable) { + properties = new TreeMap<>(); + + for(Mark mark: marks) { + mark.addSelfProperties(true, properties); + } + + for(FlexibleListProperty property:properties.values()) + { + if(property.getName().matches(PropertyName.getRegExpr("x")) ) { + id_x = new PropertyName(property.getName()); + if(id_y != null) break; + } + else if(property.getName().matches(PropertyName.getRegExpr("y")) ) { + id_y = new PropertyName(property.getName()); + if(id_x != null) break; + } + } + + this.common = common; + this.variable = variable; + + shared = new TreeMap<>(); + bindings = new TreeMap<>(); + + merge(); + + bindGroupPublic(marks); + } + + + public CollectionPropertyStructure(Collection<Mark> marks, SharingStrategy strategy) { + properties = new TreeMap<>(); + + for(Mark mark: marks) { + mark.addSelfProperties(true, properties); + } + + for(FlexibleListProperty property:properties.values()) + { + if(property.getName().matches(PropertyName.getRegExpr("x"))) { + id_x = new PropertyName(property.getName()); + if(id_y != null) break; + } + else if(property.getName().matches(PropertyName.getRegExpr("y"))) { + id_y = new PropertyName(property.getName()); + if(id_x != null) break; + } + } + + common = new TreeSet<>(); + variable = new TreeSet<>(); + shared = new TreeMap<>(); + bindings = new TreeMap<>(); + + strategy.share(properties.values(), common, variable); + merge(); + + bindGroupPublic(marks); + } + + //TODO + public void refresh(SharingStrategy strategy) { + SortedSet<PropertyName> common_ = new TreeSet<>(); + SortedSet<PropertyName> variable_ = new TreeSet<>(); + strategy.share(properties.values(), common_, variable_); + + for(PropertyName name: properties.keySet()) { + if(common.contains(name) && !common_.contains(name)) { + moveToVariable(name); + } + else if(common_.contains(name) && !common.contains(name)) { + moveToCommon(name); + } + } + } + + + private void bindGroupPublic(Collection<Mark> marks) { + if(marks.iterator().next() instanceof VisGroup) { + publicBindings = new TreeMap<>(); + + for(FlexibleListProperty property:properties.values()) { + ArrayList<Property> pubproperties = new ArrayList<>(); + for(Property p:property.getValue()) { + pubproperties.add(((Shareable)p).getPublicProperty()); + } + + // TODO: mode work needed.... + GroupBinding binding = new GroupBinding(pubproperties, false); + publicBindings.put(property.getPropertyName(), binding); + binding.rebind(); + } + } + } + + private void refreshPublicBindings() { + if(publicBindings != null) { + for(FlexibleListProperty property:properties.values()) { + ArrayList<Property> pubproperties = new ArrayList<>(); + for(Property p: property.getValue()) { + pubproperties.add(((Shareable)p).getPublicProperty()); + } + + GroupBinding binding = publicBindings.get(property.getPropertyName()); + binding.resetProperties(pubproperties); + } + } + } + + public void update(Mark mark, SharingStrategy strategy) { + mark.addSelfProperties(true, properties); + strategy.share(properties.values(), common, variable); + merge(); + } + + @Override + public void addMark(Mark mark, SharingStrategy strategy) { + mark.addSelfProperties(true, properties); + for(PropertyName key: common) { + destroyBindings(key); + } + +// common.clear(); +// variable.clear(); + shared.clear(); + bindings.clear(); + +// strategy.share(properties.values(), common, variable); + merge(); + + refreshPublicBindings(); + } + + @Override + public void addMark(Mark mark) { + mark.addSelfProperties(true, properties); + + for(PropertyName name: common) { + FlexibleListProperty column = properties.get(name); + if(column.size() > 0) { + column.get(mark.getPos()).bindBidirectional(shared.get(name)); + } + } + + refreshPublicBindings(); + } + + @Override + public void removeMark(Mark mark) { + mark.removeSelfProperties(properties); // TODO: unnecessary to remove at the higher level groups + + rebuildBindings(); + refreshPublicBindings(); + } + + @Override + public void rebuildBindings() { + for(PropertyName key: common) { + destroyBindings(key); + } + + bindings.clear(); + merge(); + } + + + // TODO: not complete + public boolean areCommon(Property p1, Property p2) { + PropertyName name = new PropertyName(p1.getName()); + if(common.contains(name) && p1.getValue().equals(p2.getValue())) return true; + else { + return false; + } + } + + @Override + public void moveToCommon(PropertyName name) { + if(common.contains(name)) return; + if(name.isHeight()) { + VisBody collection = container.getRootVirtualGroup(); + if(collection instanceof LineConnectedCollection && + ((LineConnectedCollection)collection).hasConnections()) { + return; + + } + } + + fixYConstraint(name); + fixXConstraint(name); + + variable.remove(name); + common.add(name); + merge(); + + updateSiblings(name, true); + updateChildren(name); + + flagProperty.set(!flagProperty.get()); + + if(name.isWidth() || name.isHeight()) { + FlexibleListProperty property = properties.get(name); + ConstrainedDoubleProperty prop = ((ConstrainedDoubleProperty)(property.flatten().get(0))); + + prop.updateValue(prop.getValue()); + } + } + + @Override + public void moveToVariable(PropertyName name) { + if(variable.contains(name)) return; + + common.remove(name); + variable.add(name); + destroyBindings(name); + merge(); + + updateSiblings(name, false); + + flagProperty.set(!flagProperty.get()); + + if(name.isHeight() || name.isWidth()) { + FlexibleListProperty list = properties.get(name); + Property property = list.flatten().get(0); + ShapeMark mark = (ShapeMark)property.getBean(); + if(mark.ratiolock.get()) { + if(name.isWidth()) moveToVariable(new PropertyName(mark.height.getName())); + else moveToVariable(new PropertyName(mark.width.getName())); + } + } + } + + + private void updateChildren(PropertyName name){ // TODO: check how I can link heterogenuos groups!!! + if(container instanceof VisCollection) { + VisCollection vgroup = (VisCollection) container; + if(vgroup.getComponents().size() < 1) return; + Mark mark = vgroup.getComponents().get(0); + if(mark instanceof VisCollection) { + boolean incommon = ((VisCollection) mark).getChildPropertyStructure().common.contains(name); + for(int i = 1; i < vgroup.getComponents().size(); ++i) { + mark = vgroup.getComponents().get(i); + if(mark instanceof VisCollection && ((VisCollection) mark).getChildPropertyStructure().common.contains(name) != incommon) { + if(incommon) ((VisCollection) mark).getChildPropertyStructure().moveToCommon(name); + else ((VisCollection) mark).getChildPropertyStructure().moveToVariable(name); + } + } + } + } + } + + + private void updateSiblings(PropertyName name, boolean toCommon) { + if(container instanceof VisCollection) { + Container parent = ((VisCollection)container).getContainer(); + if(parent instanceof VisCollection) { + VisCollection vgroup = ((VisCollection)parent); + // if(vgroup.getChildPropertyStructure().getCommon().contains(name)) { // Removed!!! + + for(Mark mark: vgroup.getComponents()) { + if(mark instanceof VisCollection && mark != container) { + if(toCommon) ((VisCollection)mark).getChildPropertyStructure().moveToCommon(name); + else ((VisCollection)mark).getChildPropertyStructure().moveToVariable(name); + + // + } + } + // } + } + } + } + + public List<Property> getMerged(){ + List<Property> flat = new ArrayList<>(); + + for(Property property : merged) { + if(property instanceof FlexibleListProperty && container instanceof VisCollection) { + flat.add(((FlexibleListProperty)property).getCompact(((VisCollection) container).getComponentAt(0))); + } else flat.add(property); + } + + return flat; + } + + + @Override + public void destroyAllBindings() { + for(PropertyName name:common) { + destroyBindings(name); + } + } + + + // TODO: I need to change the bindings mechanism + private void destroyBindings(PropertyName name) { // TODO:... + Property prop = shared.get(name); + + FlexibleListProperty column = properties.get(name); + if(column instanceof NestedListProperty) { + DimensionalBinding binding = bindings.get(name); + binding.destroy(); + bindings.remove(name); + // unmerge((NestedListProperty)column, id); + } + else if(column != null){ + for(int i = 0; i < column.size(); ++i) { + column.get(i).unbindBidirectional(prop); + } + } + + shared.remove(name); + } + + private void merge() { + merged = new ArrayList<>(); + + for(PropertyName name: common) { + FlexibleListProperty column = properties.get(name); + if(column == null) continue; + if(column instanceof NestedListProperty) { + FlexibleListProperty prop = (FlexibleListProperty)shared.get(name); + if(prop != null) merged.add(prop); + else { + // Find the longest list as it has information for more items!!! + int max = 0; + int index = 0; + for(int i = 0; i < column.size(); ++i) { + int size = (((NestedListProperty)column).getPropertyList(i)).size(); + if(size > max) { + index = i; + max = size; + } + } + + prop = (FlexibleListProperty)PropertyCloner.replicate(column.get(index)); + + merged.add(prop); + shared.put(name, prop); + bindings.put(name, new DimensionalBinding(this, column, prop.flattenFlex())); + } + } + else { + Property prop = shared.get(name); + if(prop!= null) merged.add(prop); + else if(column.size() > 0) { + prop = PropertyCloner.replicate(column.get(0)); + + merged.add(prop); + shared.put(name, prop); + for(int i = 0; i < column.size(); ++i) { + column.get(i).bindBidirectional(prop); + } + } + } + } + } + + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("Common: "); + for(PropertyName name: common) buffer.append(properties.get(name).getName() + " = " + properties.get(name).get() + ", "); + buffer.append("\nVariable: "); + for(PropertyName name: variable) buffer.append(properties.get(name).getName()+ " = " + properties.get(name).get() + " "); + + return buffer.toString(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/DefaultNestingStrategy.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/DefaultNestingStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..918a16c708f8a4b84b7982837351b0504add0774 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/DefaultNestingStrategy.java @@ -0,0 +1,13 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.List; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.Mark; + +public class DefaultNestingStrategy extends NestingStrategy { + + public static Container getRoot(List<Mark> marks) { + return marks.get(0).getRoot(); + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/DefaultSharingStrategy.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/DefaultSharingStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..95082307def42e911006e62a7394f2a438cfd1d0 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/DefaultSharingStrategy.java @@ -0,0 +1,189 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.PositionCoords; +import fr.inria.structgraphics.graphics.ReferenceCoords; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.graphics.XMarkComparator; +import fr.inria.structgraphics.graphics.YMarkComparator; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.DoubleListProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.NestedListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import fr.inria.structgraphics.ui.viscanvas.groupings.Grouping.Type; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.scene.paint.Color; + +public class DefaultSharingStrategy extends SharingStrategy { + + public final static double MAX_VARIANCE = 15; + + protected boolean isVariable(FlexibleListProperty column) { + if(column.isEmpty()) return false; + else if(column.getHiddenProperty().get()) return true; + + if(column instanceof DoubleListProperty) { + double mean = ((DoubleListProperty) column).average(); + if(column.getName().startsWith("thickness")) { + if(((DoubleListProperty) column).variance(mean) == 0) return false; + else return true; + } + else { + if(((DoubleListProperty) column).variance(mean) < MAX_VARIANCE) return false; + else return true; + } + + } + else if(column instanceof NestedListProperty) { + List<FlexibleListProperty> transposed = ((NestedListProperty)column).transpose(); + + for(FlexibleListProperty listProperty:transposed) { + if(isVariable(listProperty)) return true; + } + return false; + } + else if(column.allEqual()) return false; + + return true; + } + + + /* + * Create a VisCollection Object + */ + + @Override + public VisCollection createCollection(ObservableList<Mark> marks) { + ReferenceStructureCreator creator = new ReferenceStructureCreator(marks, Type.Collection); + XReferenceStructure xstruct = creator.getXReferenceStructure(); + YReferenceStructure ystruct = creator.getYReferenceStructure(); + + SortedList<Mark> sorted; + if(xstruct.isShared()) sorted = new SortedList<>(marks, new YMarkComparator()); + else sorted = new SortedList<>(marks, new XMarkComparator()); + + VisCollection group = new LineConnectedCollection(DefaultNestingStrategy.getRoot(sorted), true); + //new VisCollection(DefaultNestingStrategy.getRoot(sorted)); + group.setLevel(sorted.get(0).getLevel() + 1); + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + + group.setRefCoords(refCoords); + + PositionCoords coords = new PositionCoords(group, xstruct.getRefValue(), ystruct.getRefValue()); + coords.xRef.set(RefX.Left); + coords.yRef.set(RefY.Bottom); + group.setCoords(coords); + + for(Mark mark: sorted) { + // TODO: Does it work? + if(!(mark instanceof VisBody)) { + mark.getCoords().xRef.set(xstruct.getRef()); + mark.getCoords().yRef.set(ystruct.getRef()); + } + + group.attach(mark, xstruct.isShared(), ystruct.isShared()); + } + + group.refresh(); + + CollectionPropertyStructure propertyStructure = new CollectionPropertyStructure(sorted, this); + group.setPropertyStructure(propertyStructure); + group.initProperties(); + group.initVisibility(); + + group.getConstraintXProperty().set(xstruct.constraint); + group.getConstraintYProperty().set(ystruct.constraint); + + propertyStructure.fixXSharing(); + propertyStructure.fixYSharing(); + + group.strokePaint.set(Color.color(.85, .8, .8, .5)); + + group.getRoot().update(); + + return group; + } + + + /* + * Create a VisGroup object + */ + @Override + public VisGroup createGroup(ObservableList<Mark> marks) { + // TODO I just repeat here the collection stuff, but this needs to change + ReferenceStructureCreator creator = new ReferenceStructureCreator(marks, Type.Group); + XReferenceStructure xstruct = creator.getXReferenceStructure(); + YReferenceStructure ystruct = creator.getYReferenceStructure(); + + SortedList<Mark> sorted; + if(xstruct.isShared()) sorted = new SortedList<>(marks, new YMarkComparator()); + else sorted = new SortedList<>(marks, new XMarkComparator()); + + VisGroup group = new VisGroup(DefaultNestingStrategy.getRoot(sorted), true); + group.setLevel(sorted.get(0).getLevel() + 1); + ReferenceCoords refCoords = new ReferenceCoords(RefX.Left, RefY.Bottom); + + group.setRefCoords(refCoords); + + PositionCoords coords = new PositionCoords(group, xstruct.getRefValue(), ystruct.getRefValue()); + coords.xRef.set(RefX.Left); + coords.yRef.set(RefY.Bottom); + group.setCoords(coords); + + for(Mark mark: sorted) { + // TODO: Does it work? + if(!(mark instanceof VisBody)) { + // mark.getCoords().xRef.set(xstruct.getRef()); + // mark.getCoords().yRef.set(ystruct.getRef()); + } + + group.attach(mark, xstruct.isShared(), ystruct.isShared()); + } + + group.refresh(); + + GroupPropertyStructure propertyStructure = new GroupPropertyStructure(sorted, this); + group.setPropertyStructure(propertyStructure); + group.initProperties(); + group.initVisibility(); + group.getConstraintXProperty().set(xstruct.constraint); + group.getConstraintYProperty().set(ystruct.constraint); + + propertyStructure.fixXSharing(); + propertyStructure.fixYSharing(); + + group.getRoot().update(); + + return group; + } + + + @Override + public void share(Map<PropertyName, FlexibleListProperty> properties, + TreeMap<PropertyName, ArrayList<GroupBinding>> bindings, + SortedSet<PropertyName> common, SortedSet<PropertyName> variable) { + + for(PropertyName name:properties.keySet()) { + FlexibleListProperty property = properties.get(name); + ArrayList<GroupBinding> groups = property.createGroupBindings(); + + if(groups.size() < property.size()) { + bindings.put(name, groups); + common.add(name); + } else variable.add(name); + } + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/DimensionalBinding.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/DimensionalBinding.java new file mode 100644 index 0000000000000000000000000000000000000000..656afe887a4f5bb63e9742ab235d91bce0a305ab --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/DimensionalBinding.java @@ -0,0 +1,76 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.types.FlexibleListProperty; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +public class DimensionalBinding { + private CollectionPropertyStructure propertyStructure; + private List<PropertyChangeListener> listeners = new ArrayList<>(); + + public DimensionalBinding(CollectionPropertyStructure propertyStructure, FlexibleListProperty listProperty, FlexibleListProperty copies) { + this.propertyStructure = propertyStructure; + + List<FlexibleListProperty> transposed = listProperty.transpose(); + + int i = 0; + for(FlexibleListProperty sublist: transposed) { + addBindings(sublist, copies.get(i++)); + } + } + + private void addBindings(FlexibleListProperty sublist, Property copy) { + PropertyChangeListener listener = new PropertyChangeListener(sublist, copy); + + for(Property property: sublist) { + property.addListener(listener); + property.setValue(copy.getValue()); + } + + copy.addListener(listener); + + listeners.add(listener); + } + + class PropertyChangeListener implements ChangeListener { + private FlexibleListProperty sublist; + private Property copy; + + public PropertyChangeListener(FlexibleListProperty sublist, Property copy) { + this.sublist = sublist; + this.copy = copy; + } + + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) { + // if(propertyStructure.isLocked()) return; + + for(Property property: sublist) { + if(observable != property) { + if(!property.getValue().equals(newValue)) property.setValue(newValue); + } + } + if(observable != copy && !copy.getValue().equals(newValue)) copy.setValue(newValue); + } + + public void disable(){ + for(Property property: sublist) { + property.removeListener(this); + } + copy.removeListener(this); + } + } + + public void destroy() { + for(PropertyChangeListener listener:listeners) + listener.disable(); + + listeners.clear(); + } + +} + diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/FlowConnection.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/FlowConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..bc6d68f5ef4038022dfb241fd667f743430a2ff9 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/FlowConnection.java @@ -0,0 +1,143 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.ConnectionWeightProperty; +import fr.inria.structgraphics.ui.utils.FlowConnectionBinding; + +public class FlowConnection implements Comparable<FlowConnection> { + + private ShapeMark origin, destination; + public ConnectionWeightProperty weight; + private LineConnectedCollection collection; + private FlowConnections parent; + + public FlowConnection(LineConnectedCollection collection, ShapeMark origin, ShapeMark destination) { + this.collection = collection; + this.origin = origin; + this.destination = destination; + weight = new ConnectionWeightProperty(this); + weight.set(1); + } + + public void destroy() { + collection.removeConnection(this); + parent.remove(this); + } + + public void detachFront() { + FlowConnectionBinding binding = destination.getFlowConnectionsBinding(); + binding.removeInwardConnection(this); + + if(parent.hasInwardConnections(destination)) { + destination.height.updateValue(destination.height() - weight.get()); + } + } + + public void detachBack() { + FlowConnectionBinding binding = origin.getFlowConnectionsBinding(); + binding.removeOutwardConnection(this); + + if(parent.hasOutwardConnections(origin)) { + origin.height.updateValue(origin.height() - weight.get()); + } + + origin.updateLabels(); + } + + public double startX() { + double startX = origin.right(); + + for(VisBody parent = (VisBody)origin.getContainer(); parent != collection; parent = (VisBody)parent.getContainer()) { + startX += parent.coords.getX(); + } + + return startX; + } + + public double startY() { + double startY = -getOrigin().centerY(); + + for(VisBody parent = (VisBody)origin.getContainer(); parent != collection; parent = (VisBody)parent.getContainer()) { + startY -= parent.coords.getY(); + } + + return startY; + } + + public double startYBottom() { + return startY() - Math.abs(getOrigin().height())/2; + } + + public double endYBottom() { + return endY() - Math.abs(getDestination().height())/2; + } + + public double endX() { + double endX = getDestination().left(); + + for(VisBody parent = (VisBody)destination.getContainer(); parent != collection; parent = (VisBody)parent.getContainer()) { + endX += parent.coords.getX(); + } + + return endX; + } + + public double endY() { + double endY = -getDestination().centerY(); + + for(VisBody parent = (VisBody)destination.getContainer(); parent != collection; parent = (VisBody)parent.getContainer()) { + endY -= parent.coords.getY(); + } + + return endY; + } + + public ShapeMark getOrigin() { + return origin; + } + + public ShapeMark getDestination() { + return destination; + } + + public ConnectionWeightProperty weightProperty() { + return weight; + } + + @Override + public int compareTo(FlowConnection conn) { + if(equals(conn)) return 0; + + int comp = origin.id.get().compareTo(conn.origin.id.get()); + if(comp == 0) return destination.id.get().compareTo(conn.destination.id.get()); + else return comp; + } + + @Override + public boolean equals(Object o) { + if(o instanceof FlowConnection) { + FlowConnection conn = (FlowConnection)o; + if(conn.origin == origin && conn.destination == destination) { + return true; + } + } + + return false; + } + + public void setParent(FlowConnections flowConnections) { + this.parent = flowConnections; + } + + public LineConnectedCollection getCollection() { + return collection; + } + + + @Override + public int hashCode() { // Hmmm. This could generate a bug... very unlikely though... + return origin.hashCode() + destination.hashCode(); + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/FlowConnections.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/FlowConnections.java new file mode 100644 index 0000000000000000000000000000000000000000..8a8df56de9dacee78805e0c77d1a3bd0908a34d3 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/FlowConnections.java @@ -0,0 +1,321 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import javafx.beans.property.Property; + +public class FlowConnections extends HashSet<FlowConnection> implements Comparator<FlowConnection>{ + + protected LineConnectedCollection collection; + + public FlowConnections(LineConnectedCollection collection) { + super(); + this.collection = collection; + } + + // TODO: This is a very first draft... + public FlowConnection addConnection(ShapeMark origin, ShapeMark destination, boolean clever) { + FlowConnection conn = new FlowConnection(collection, origin, destination); + conn.setParent(this); + + if(!clever) { + add(conn); + return conn; + } + + if(!contains(conn)) { + origin.lockListeners(true); + destination.lockListeners(true); + + if(!hasOutwardConnections(origin)) { + if(!hasInwardConnections(origin) && !hasInwardConnections(destination)) { + if(hasOutwardConnections(destination)) { + conn.weightProperty().set(destination.height()); + origin.height.updateValue(destination.height()); + } else { + conn.weightProperty().set(origin.height()); + destination.height.updateValue(origin.height()); + } + } else { + conn.weightProperty().set(origin.height()); + if(!hasInwardConnections(destination) /*&& !hasFromConnections(destination)*/) { + destination.lockListeners(false); + destination.height.updateValue(origin.height()); + //Set<FlowConnection> fromDestination = from(destination); + //for(FlowConnection fo:fromDestination) w += fo.weightProperty().get(); + } + else { + destination.height.updateValue(destination.height() + origin.height()); + } + } + } else { // NOT fully complete + if(hasInwardConnections(destination)) { + Set<FlowConnection> fromOrigin = from(origin); + double w = 0; + for(FlowConnection fo:fromOrigin) w += fo.weightProperty().get(); + double diff = origin.height() - w; + if(diff == 0) { + diff = 10; + conn.weightProperty().set(diff); + destination.height.updateValue(destination.height() + diff); + + //origin.lockListeners(false); + origin.height.updateValue(origin.height() + diff); + } else { + conn.weightProperty().set(diff); + destination.height.updateValue(destination.height() + diff); + } + } + else { + Set<FlowConnection> fromOrigin = from(origin); + double w = 0; + for(FlowConnection fo:fromOrigin) w += fo.weightProperty().get(); + double diff = origin.height() - w; + + if(diff > 0) { + conn.weightProperty().set(diff); + destination.height.updateValue(diff); + } else { + conn.weightProperty().set(destination.height()); + + //origin.lockListeners(false); + origin.height.updateValue(destination.height() + origin.height()); + } + } + } + + add(conn); + + origin.lockListeners(false); + destination.lockListeners(false); + + return conn; + } + else return null; + } + + public void removeConnection(FlowConnection conn) { + this.remove(conn); + } + + public void removeConnection(ShapeMark origin, ShapeMark destination) { + this.remove(new FlowConnection(collection, origin, destination)); + } + + public void removeFrom(ShapeMark origin) { + Set<FlowConnection> connections = from(origin); + this.removeAll(connections); + } + + + public void removeTo(ShapeMark destination) { + Set<FlowConnection> connections = to(destination); + this.removeAll(connections); + } + + + public boolean hasInwardConnections(ShapeMark shape) { + for(FlowConnection conn: this) { + if(conn.getDestination() == shape) return true; + } + + return false; + } + + public boolean hasOutwardConnections(ShapeMark shape) { + for(FlowConnection conn: this) { + if(conn.getOrigin() == shape) return true; + } + + return false; + } + + public Set<FlowConnection> from(ShapeMark origin){ + TreeSet subset = new TreeSet<>(); + + for(FlowConnection conn: this) { + if(conn.getOrigin() == origin) + subset.add(conn); + } + + return subset; + } + + + public Set<FlowConnection> to(ShapeMark destination){ + TreeSet subset = new TreeSet<>(); + + for(FlowConnection conn: this) { + if(conn.getDestination() == destination) + subset.add(conn); + } + + return subset; + } + + /* + * This is used for the spreadsheet + */ + public Map<PropertyName, FlexibleListProperty> getCompactVariableList(ArrayList<PropertyName> names){ + Map<PropertyName, FlexibleListProperty> flat = new TreeMap<>(); + + Set<FlowConnection> connections = this.sortByX(); + + if(names.contains(new PropertyName("id"))) { + List<Property> ids = new ArrayList<>(); + for(FlowConnection conn: connections) { + List<Property> source_destination = new ArrayList<>(); + source_destination.add(conn.getOrigin().id); + source_destination.add(conn.getDestination().id); + ids.add(FlexibleListProperty.createList(collection, source_destination)); + } + FlexibleListProperty connectionListProperty = FlexibleListProperty.createList(collection, ids); + flat.put(connectionListProperty.getPropertyName(), connectionListProperty); + } + if(names.contains(new PropertyName("weight"))) { + List<Property> weights = new ArrayList<>(); + for(FlowConnection conn: connections) { + weights.add(conn.weightProperty()); + } + FlexibleListProperty weightListProperty = FlexibleListProperty.createList(collection, weights); + flat.put(weightListProperty.getPropertyName(), weightListProperty); + } + + return flat; + } + + + /* + * This is used for the inspector + */ + + public Map<PropertyName, FlexibleListProperty> getVariableList(){ + Map<PropertyName, FlexibleListProperty> flat = new TreeMap<>(); + + // 1: First, we add the IDs of all nodes!!! + //FlexibleListProperty ids = FlexibleListProperty.createList(bean, properties); + List<Property> ids1 = new ArrayList<>(); + List<Property> ids2 = new ArrayList<>(); + List<Property> weights = new ArrayList<>(); + for(FlowConnection conn: this.sortByX()) { + ids1.add(conn.getOrigin().id); + ids2.add(conn.getDestination().id); + weights.add(conn.weightProperty()); + } + + FlexibleListProperty originListProperty = FlexibleListProperty.createList(collection, ids1); + FlexibleListProperty destinationListProperty = FlexibleListProperty.createList(collection, ids2); + FlexibleListProperty weightListProperty = FlexibleListProperty.createList(collection, weights); + + flat.put(/*originListProperty.getPropertyName()*/new PropertyName("source"), originListProperty); + flat.put(/*destinationListProperty.getPropertyName()*/new PropertyName("destination"), destinationListProperty); + flat.put(weightListProperty.getPropertyName(), weightListProperty); + + // TO add the extra property for weights + + return flat; + } + + // Sort all connections by the X coordinate of their origin, destination + public Set<FlowConnection> sortByX(){ + TreeSet<FlowConnection> subset = new TreeSet<FlowConnection>(new Comparator<FlowConnection>() { + + @Override + public int compare(FlowConnection conn1, FlowConnection conn2) { + ShapeMark origin1 = conn1.getOrigin(); + ShapeMark origin2 = conn2.getOrigin(); + + if(origin1 != origin2) { + double x1 = origin1.coords.getX(); + double x2 = origin2.coords.getX(); + + if(x1 < x2) return -1; + else if(x1 > x2) return 1; + else if (origin1.coords.getY() <= origin2.coords.getY()) return -1; + else return 1; + } else { + ShapeMark dest1 = conn1.getDestination(); + ShapeMark dest2 = conn2.getDestination(); + + if(dest1 == dest2) return 0; + + double x1 = dest1.coords.getX(); + double x2 = dest2.coords.getX(); + + if(x1 < x2) return -1; + else if(x1 > x2) return 1; + else if (dest1.coords.getY() <= dest2.coords.getY()) return -1; + else return 1; + } + } + }); + + for(FlowConnection conn: this) { + subset.add(conn); + } + + return subset; + } + + + // Sort all connections by the Y coordinate of their origin, destination + // Also, rest + public Set<FlowConnection> sortByY(){ + TreeSet<FlowConnection> subset = new TreeSet<FlowConnection>(selfAsComparator()); + + for(FlowConnection conn: this) { + subset.add(conn); + } + + return subset; + } + + private Comparator<FlowConnection> selfAsComparator(){ + return this; + } + + @Override + public int compare(FlowConnection conn1, FlowConnection conn2) { + ShapeMark origin1 = conn1.getOrigin(); + ShapeMark origin2 = conn2.getOrigin(); + + if(origin1 != origin2) { + double y1 = origin1.getYIn(collection);//origin1.coords.getY(); + double y2 = origin2.getYIn(collection);//origin2.coords.getY(); + + if(y1 < y2) return 1; + else if(y1 > y2) return -1; + else if (origin1.getXIn(collection) <= origin2.getXIn(collection)) return 1; + //else if (origin1.coords.getX() <= origin2.coords.getX()) return 1; + else return -1; + } else { + ShapeMark dest1 = conn1.getDestination(); + ShapeMark dest2 = conn2.getDestination(); + + if(dest1 == dest2) return 0; + + double y1 = dest1.getYIn(collection);//dest1.coords.getY(); + double y2 = dest2.getYIn(collection);//dest2.coords.getY(); + + if(y1 < y2) return 1; + else if(y1 > y2) return -1; + else if (dest1.getXIn(collection) <= dest2.getXIn(collection)) return 1; + //else if (dest1.coords.getX() <= dest2.coords.getX()) return 1; + else return -1; + } + } + + + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/GroupPropertyStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/GroupPropertyStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..f76643d9176df0444ec37f26644b3e4733c00d6c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/GroupPropertyStructure.java @@ -0,0 +1,266 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.ConstrainedDoubleProperty; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.Shareable; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import javafx.beans.property.Property; + +public class GroupPropertyStructure extends PropertyStructure { + + protected TreeMap<PropertyName, ArrayList<GroupBinding>> bindings = new TreeMap<>(); + + public GroupPropertyStructure(Collection<Mark> marks) { + this(marks, new DefaultSharingStrategy()); + } + + public GroupPropertyStructure(Collection<Mark> marks, SharingStrategy strategy) { + properties = new TreeMap<>(); + + for(Mark mark: marks) { + mark.addSelfProperties(false, properties); + + if(mark instanceof VisGroup) { + ((VisGroup) mark).getChildPropertyStructure().makeAllVariable(); + } + } + + for(FlexibleListProperty property:properties.values()) + { + if(property.getName().matches(PropertyName.getRegExpr("x"))) { + id_x = new PropertyName(property.getName()); + if(id_y != null) break; + } + else if(property.getName().matches(PropertyName.getRegExpr("y"))) { + id_y = new PropertyName(property.getName()); + if(id_x != null) break; + } + } + + common = new TreeSet<>(); + variable = new TreeSet<>(); + strategy.share(properties, bindings, common, variable); + } + + public GroupPropertyStructure(Collection<Mark> marks, GroupPropertyStructure structure) { + properties = new TreeMap<>(); + + for(Mark mark: marks) { + mark.addSelfProperties(false, properties); + } + + for(FlexibleListProperty property:properties.values()) + { + if(property.getName().matches(PropertyName.getRegExpr("x"))) { + id_x = new PropertyName(property.getName()); + if(id_y != null) break; + } + else if(property.getName().matches(PropertyName.getRegExpr("y"))) { + id_y = new PropertyName(property.getName()); + if(id_x != null) break; + } + } + + common = new TreeSet<>(); + common.addAll(structure.common); + variable = new TreeSet<>(); + variable.addAll(structure.variable); + + rebuildBindings(); + } + + + public GroupPropertyStructure(Collection<Mark> marks, SortedSet<PropertyName> common, SortedSet<PropertyName> variable) { + properties = new TreeMap<>(); + + for(Mark mark: marks) { + mark.addSelfProperties(false, properties); + } + + for(FlexibleListProperty property:properties.values()) + { + if(property.getName().matches(PropertyName.getRegExpr("x")) ) { + id_x = new PropertyName(property.getName()); + if(id_y != null) break; + } + else if(property.getName().matches(PropertyName.getRegExpr("y")) ) { + id_y = new PropertyName(property.getName()); + if(id_x != null) break; + } + } + + this.common = common; + this.variable = variable; + // rebuildBindings(); + } + + + public ArrayList<GroupBinding> getGroups(PropertyName name) { + return bindings.get(name); + } + + public ArrayList<Property> getBindingsOf(Property property){ + ArrayList<GroupBinding> groups = bindings.get(new PropertyName(property.getName())); + for(GroupBinding group: groups) { + if(group.contains(property)) + return group.getProperties(); + } + + return null; + } + + @Override + public void addMark(Mark mark, SharingStrategy strategy) { + this.addMark(mark); + } + + @Override + public void addMark(Mark mark) { + mark.addSelfProperties(false, properties); + List<Property> props = mark.getProperties(); + + for(Property prop: props) { + PropertyName name = new PropertyName(prop.getName()); + if(variable.contains(name)) break; + + ArrayList<GroupBinding> groups = bindings.get(name); + if(!groups.isEmpty()) { + for(GroupBinding group: groups) { + if(group.hasValue(prop)) { + group.add(prop); + break; + } + } + } + } + } + + @Override + public void removeMark(Mark mark) { + mark.removeSelfProperties(properties); + List<Property> props = mark.getProperties(); + + for(Property prop: props) { + PropertyName name = new PropertyName(prop.getName()); + if(variable.contains(name)) break; + + ArrayList<GroupBinding> groups = bindings.get(name); + if(!groups.isEmpty()) { + GroupBinding group_ = null; + + for(GroupBinding group: groups) { + if(group.hasValue(prop)) { + group.remove(prop); + if(group.isEmpty()) group_ = group; + break; + } + } + + if(group_ != null) groups.remove(group_); + } + } + } + + public void setBindings(TreeMap<PropertyName, ArrayList<GroupBinding>> bindings) { + this.bindings = bindings; + } + + @Override + public void destroyAllBindings() { + for(ArrayList<GroupBinding> groups:bindings.values()) { + for(GroupBinding group:groups) { + group.unbind(); + } + groups.clear(); + } + + bindings.clear(); + } + + @Override + public void rebuildBindings() { + for(PropertyName name: properties.keySet()) { + if(common.contains(name)) { + FlexibleListProperty prop = properties.get(name); + bindings.put(name, prop.createGroupBindings()); + } + } + } + + @Override + public void moveToCommon(PropertyName name) { + if(common.contains(name)) return; + + // TODO: ????? + fixYConstraint(name); + fixXConstraint(name); + + variable.remove(name); + common.add(name); + + FlexibleListProperty prop = properties.get(name); + bindings.put(name, prop.createGroupBindings()); + + flagProperty.set(!flagProperty.get()); + + if(name.isWidth() || name.isHeight()) { + FlexibleListProperty property = properties.get(name); + ConstrainedDoubleProperty dprop = ((ConstrainedDoubleProperty)(property.flatten().get(0))); + + dprop.updateValue(dprop.getValue()); + } + } + + @Override + public void moveToVariable(PropertyName name) { + if(variable.contains(name)) return; + common.remove(name); + variable.add(name); + + ArrayList<GroupBinding> groups = bindings.get(name); + if(groups != null) { + for(GroupBinding group:groups) group.unbind(); + groups.clear(); + bindings.remove(name); + } + + flagProperty.set(!flagProperty.get()); + + } + + public void makeAllVariable() { + for(PropertyName propName:properties.keySet()) { + if(common.contains(propName)) moveToVariable(propName); + } + } + + public Collection<Property> getFlattenedProperties(){ + Collection<Property> flattened = new ArrayList<>(); + + for(PropertyName name: properties.keySet()) { + FlexibleListProperty property = properties.get(name); + if(common.contains(name)) { + ArrayList<Property> propertyList = new ArrayList<>(); + + ArrayList<GroupBinding> groups = bindings.get(name); + for(GroupBinding group: groups) { + if(!group.isEmpty()) propertyList.add(group.firstProperty()); + } + + flattened.add(FlexibleListProperty.createList(/*property.getBean()*/ container, propertyList)); + } else flattened.add(property); + } + + return flattened; + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/Grouping.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/Grouping.java new file mode 100644 index 0000000000000000000000000000000000000000..188e184407b3544d5ebd65d90a7d2ca0d05a7b86 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/Grouping.java @@ -0,0 +1,21 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import fr.inria.structgraphics.graphics.Mark; +import javafx.collections.ObservableList; + +public class Grouping { + + public enum Type {Collection, Group} + + protected Mark markGroup; + protected CollectionPropertyStructure structure; + + public Grouping(ObservableList<Mark> marks, Type type) { + if(type == Type.Collection) markGroup = new DefaultSharingStrategy().createCollection(marks); + else markGroup = new DefaultSharingStrategy().createGroup(marks); + } + + public Mark getGroup() { + return markGroup; + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/NestingStrategy.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/NestingStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..9cade6f25d8ba0d88b128fbad97ceef7cf0d96b9 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/NestingStrategy.java @@ -0,0 +1,5 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +public abstract class NestingStrategy { + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/NestingStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/NestingStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..cae854058d4277204cf39b14f6b5d54b9bd46e00 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/NestingStructure.java @@ -0,0 +1,15 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.PositionCoords; + +public class NestingStructure { + protected PositionCoords containerCoords, childrenCoords; + + public NestingStructure(ArrayList<Mark> marks, NestingStrategy strategy) { + + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/PropertyStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/PropertyStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..0a11a65c256f3c0bc16fabe2ea0aa44f351baf20 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/PropertyStructure.java @@ -0,0 +1,269 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; + +public abstract class PropertyStructure { + + protected BooleanProperty flagProperty = new SimpleBooleanProperty(true); + + protected Map<PropertyName, FlexibleListProperty> properties; + protected VisBody container; + protected PropertyName id_x = null, id_y = null; + protected SortedSet<PropertyName> common, variable; + + public abstract void addMark(Mark mark, SharingStrategy strategy); + public abstract void addMark(Mark mark); + public abstract void removeMark(Mark mark); + public abstract void destroyAllBindings(); + public abstract void rebuildBindings(); + public abstract void moveToCommon(PropertyName propertyName); + public abstract void moveToVariable(PropertyName name); + + public boolean isXShared() { + return id_x != null && common.contains(id_x); + } + + public boolean isYShared() { + return id_y != null && common.contains(id_y); + } + + public FlexibleListProperty getXListProperty() { + return properties.get(id_x); + } + + public FlexibleListProperty getYListProperty() { + return properties.get(id_y); + } + + public Map<PropertyName, FlexibleListProperty> getProperties(){ + return properties; + } + + public FlexibleListProperty getProperty(PropertyName name) { + return properties.get(name); + } + + public boolean isEmpty() { + return properties.isEmpty(); + } + + + public Property getCompactFlat(PropertyName name) { + Property property = properties.get(name); + + if(property instanceof FlexibleListProperty && container instanceof VisCollection) { + Property prop = ((FlexibleListProperty)property).getCompact(((VisCollection) container).getComponentAt(0)); + if(prop instanceof FlexibleListProperty) return ((FlexibleListProperty)prop).get(0); + else return prop; + } else return property; + } + + public static PropertyStructure create(Collection<Mark> marks, PropertyStructure structure) { + if(structure instanceof CollectionPropertyStructure) return new CollectionPropertyStructure(marks, (CollectionPropertyStructure)structure); + else return new GroupPropertyStructure(marks, (GroupPropertyStructure)structure); // TODO + } + + public void setContainer(VisBody container) { + this.container = container; + } + + public BooleanProperty getFlagProperty() { + return flagProperty; + } + + public SortedSet<PropertyName> getVariable(){ + return variable; + } + + public SortedSet<PropertyName> getCommon(){ + return common; + } + + public Set<PropertyName> getNames(){ + return properties.keySet(); + } + + public Collection<Property> getCommonProperties(){ + ArrayList<Property> list = new ArrayList<>(); + for(PropertyName name:common) { + list.add(properties.get(name)); + } + + return list; + } + + + public Map<PropertyName, FlexibleListProperty> getVariableList(){ + Map<PropertyName, FlexibleListProperty> flat = new TreeMap<>(); + + for(PropertyName name: variable) { + FlexibleListProperty list = properties.get(name); + + if(list != null && list.getPublicProperty().get() && !list.getHiddenProperty().get()) + flat.put(name, (FlexibleListProperty)list.getCompact(container)); + } + + return flat; + } + + + public Map<PropertyName, FlexibleListProperty> getVariableList(Collection<PropertyName> names){ + Map<PropertyName, FlexibleListProperty> flat = new TreeMap<>(); + + for(PropertyName name: names) { + FlexibleListProperty list = properties.get(name); + + if(list != null && list.getPublicProperty().get() && !list.getHiddenProperty().get()) + flat.put(name, (FlexibleListProperty)list.getCompact2(container)); + } + + return flat; + } + + + private void addIDColumns(Map<PropertyName, FlexibleListProperty> flat, Collection<PropertyName> names) { + int maxLevel = container.getLevel(); + int minLevel = container.getBottomLevel(); + + for(int level = minLevel; level<maxLevel; ++level) { + List<Property> ids = new ArrayList<>(); + container.addIDs(ids, level); + FlexibleListProperty idsListProperty = FlexibleListProperty.createList(container, ids); + if(names == null || names.contains(idsListProperty.getPropertyName())) flat.put(idsListProperty.getPropertyName(), idsListProperty); + } + } + + public Map<PropertyName, FlexibleListProperty> getFullVariableList(){ + Map<PropertyName, FlexibleListProperty> flat = new TreeMap<>(); + + addIDColumns(flat, null); + + for(FlexibleListProperty propertyList: properties.values()) { + if(propertyList.varies(container) && propertyList.getPublicProperty().get() + && !propertyList.getHiddenProperty().get()) { + flat.put(propertyList.getPropertyName(), (FlexibleListProperty)propertyList.getExtended()); + } + } + + return flat; + } + + + public Map<PropertyName, FlexibleListProperty> getFullVariableList(Collection<PropertyName> names){ + Map<PropertyName, FlexibleListProperty> flat = new TreeMap<>(); + + addIDColumns(flat, names); + + for(PropertyName name: names) { + FlexibleListProperty list = properties.get(name); + + if(list != null && list.getPublicProperty().get() && !list.getHiddenProperty().get()) + flat.put(name, (FlexibleListProperty)list.getExtended()); + } + + return flat; + } + + public void fixYSharing() { + if(container.getComponents().get(0) instanceof VisCollection) { + VisBody child = (VisBody) container.getComponents().get(0); + PropertyName yproperty = new PropertyName(child.getComponents().get(0).coords.y.getName()); + + if(child.constraintYProperty.get() != Constraint.None && !variable.contains(yproperty)) { + moveToVariable(yproperty); + } + } else if(container.getComponents().get(0) instanceof VisGroup){ /* + VisBody child = (VisBody) container.getComponents().get(0); + + for(Mark mark:child.getComponents()) { + PropertyName yproperty = new PropertyName(mark.coords.y.getName()); + if(child.constraintYProperty.get() != Constraint.None && !variable.contains(yproperty)) { + moveToVariable(yproperty); + } + }*/ + } + } + + public void fixXSharing() { + if(container.getComponents().get(0) instanceof VisCollection) { + VisBody child = (VisBody) container.getComponents().get(0); + PropertyName xproperty = new PropertyName(child.getComponents().get(0).coords.x.getName()); + + if(child.constraintXProperty.get() != Constraint.None && !variable.contains(xproperty)) { + moveToVariable(xproperty); + } + } else if(container.getComponents().get(0) instanceof VisGroup){/* + VisBody child = (VisBody) container.getComponents().get(0); + + for(Mark mark:child.getComponents()) { + PropertyName xproperty = new PropertyName(mark.coords.x.getName()); + if(child.constraintXProperty.get() != Constraint.None && !variable.contains(xproperty)) { + moveToVariable(xproperty); + } + } + */ + } + } + + public void fixYConstraint(PropertyName name) { // TODO: Do I need that??? + if(container.getComponents().get(0) instanceof VisBody && name.isY()) { + VisBody child = (VisBody) container.getComponents().get(0); + PropertyName yproperty = new PropertyName(child.getComponents().get(0).coords.y.getName()); + + + if(child.constraintYProperty.get() == Constraint.Spacing && yproperty.equals(name)) { + child.constraintYProperty.set(Constraint.None); + } + + /* + if(child.constraintYProperty.get() != Constraint.None && yproperty.equals(name)) { + child.constraintYProperty.set(Constraint.None); + }*/ + } + } + + public void fixXConstraint(PropertyName name) { // TODO: Do I need that??? + if(container.getComponents().get(0) instanceof VisBody && name.isX()) { + VisBody child = (VisBody) container.getComponents().get(0); + PropertyName xproperty = new PropertyName(child.getComponents().get(0).coords.x.getName()); + + + if(child.constraintXProperty.get() == Constraint.Spacing && xproperty.equals(name)) { + child.constraintXProperty.set(Constraint.None); + } + /* + if(child.constraintXProperty.get() != Constraint.None && xproperty.equals(name)) { + child.constraintXProperty.set(Constraint.None); + }*/ + } + } + + + public void removeHeightSharing(ShapeMark mark) { + PropertyName heightPropertyName = new PropertyName(mark.height.getName()); + if(common.contains(heightPropertyName)) moveToVariable(heightPropertyName); + for(Mark child:container.getComponents()) { + if(child instanceof VisBody) + ((VisBody)child).getChildPropertyStructure().removeHeightSharing(mark); + } + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/ReferenceStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/ReferenceStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..2e731e6939df64789c772603a6179986f5b1938c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/ReferenceStructure.java @@ -0,0 +1,18 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import fr.inria.structgraphics.types.DistributionProperty; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; + +public class ReferenceStructure { + protected boolean shared = false; + public DistributionProperty.Constraint constraint = Constraint.None; + + public ReferenceStructure (boolean shared, Constraint constraint) { + this.shared = shared; + this.constraint = constraint; + } + + public boolean isShared() { + return shared; + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/ReferenceStructureCreator.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/ReferenceStructureCreator.java new file mode 100644 index 0000000000000000000000000000000000000000..cec6e0654dad5b0698eb5bdc0159f610d107e17c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/ReferenceStructureCreator.java @@ -0,0 +1,603 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.XMarkComparator; +import fr.inria.structgraphics.graphics.YMarkComparator; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.ui.utils.Stats; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; + +public class ReferenceStructureCreator { + + private XReferenceStructure xReferenceStructure; + private YReferenceStructure yReferenceStructure; + + public ReferenceStructureCreator(ObservableList<Mark> marks, Grouping.Type gtype) { + + double valx = marks.get(0).getCoords().getX(), valy = marks.get(0).getCoords().getY(); + + if(marks.size() == 1) { + RefX refx = marks.get(0).getCoords().getXRef(); + RefY refy = marks.get(0).getCoords().getYRef(); + + if(!(marks.get(0) instanceof VisBody)) { + switch(refx) { + case Center: + valx = marks.get(0).centerX(); + break; + case Left: + valx = marks.get(0).left(); + break; + case Right: + valx = marks.get(0).right(); + break; + } + + switch(refy) { + case Center: + valy = marks.get(0).centerY(); + break; + case Top: + valy = marks.get(0).top(); + break; + case Bottom: + valy = marks.get(0).bottom(); + break; + } + } + + xReferenceStructure = new XReferenceStructure(refx, valx, false, Constraint.None); + yReferenceStructure = new YReferenceStructure(refy, valy, true, Constraint.None); + return; + } + + SortedList<Mark> marksX = new SortedList<>(marks, new XMarkComparator()); + SortedList<Mark> marksY = new SortedList<>(marks, new YMarkComparator()); + + ///////////////////////////////// + List<Double> left = getXLeft(marksX); + Stats leftStats = new Stats(left); + + List<Double> centerx = getXCenter(marksX); + Stats centerXStats = new Stats(centerx); + + List<Double> right = getXRight(marksX); + Stats rightStats = new Stats(right); + + List<Double> bottom = getYBottom(marksY); + Stats bottomStats = new Stats(bottom); + + List<Double> centery = getYCenter(marksY); + Stats centerYStats = new Stats(centery); + + List<Double> top = getYTop(marksY); + Stats topStats = new Stats(top); + + ///////////////////////////////// + List<Double> gapsX = getXGap(marksX); + Stats gapsXStats = new Stats(gapsX); + + List<Double> gapsY = getYGap(marksY); + Stats gapsYStats = new Stats(gapsY); + ///////////////////////////////// + + //////////////////////////////////// + List<Double> topDistr = getTopDistribution(marksY); + Stats topDistrStats = new Stats(topDistr); + + List<Double> bottomDistr = getBottomDistribution(marksY); + Stats bottomDistrStats = new Stats(bottomDistr); + + List<Double> centerYDistr = getCenterYDistribution(marksY); + Stats centerYDistrStats = new Stats(centerYDistr); + + List<Double> leftDistr = getLeftDistribution(marksX); + Stats leftDistrStats = new Stats(leftDistr); + + List<Double> rightDistr = getRightDistribution(marksX); + Stats rightDistrStats = new Stats(rightDistr); + + List<Double> centerXDistr = getCenterXDistribution(marksX); + Stats centerXDistrStats = new Stats(centerXDistr); + + List<Double> widthDistr = getWidths(marksX); + Stats widthStats = new Stats(widthDistr); + ////////////////////////////// + + Constraint constraintX = Constraint.None, constraintY = Constraint.None; + RefX refx = RefX.Center; + RefY refy = RefY.Center; + boolean sharedx = false, sharedy = false; + valx = leftStats.min; + valy = bottomStats.min; + + double dx = 0; // This is to add a gap on the x-axis based on the distribution constraint (for collections) + + if(leftStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedx = true; + refx = RefX.Left; + valx = leftStats.mean; + } + else if(centerXStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedx = true; + refx = RefX.Center; + valx = centerXStats.mean; + } + else if(rightStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedx = true; + refx = RefX.Right; + valx = rightStats.mean; + } + else if(centerXDistr.size() > 1 && centerXDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Distance; + refx = RefX.Center; + dx = centerXDistrStats.mean/8; + } + else if(leftDistr.size() > 1 && leftDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Distance; + refx = RefX.Left; + dx = leftDistrStats.mean/8; + } + else if(gapsX.size() > 1 && gapsXStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Spacing; + refx = RefX.Left; + } + else if(rightDistr.size() > 1 && rightDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Distance; + refx = RefX.Right; + dx = rightDistrStats.mean/8; + } + + ////////////// Y ############################ + if(bottomStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedy = true; + refy = RefY.Bottom; + valy = bottomStats.mean; + } + else if(centerYStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedy = true; + refy = RefY.Center; + valy = centerYStats.mean; + } + else if(topStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedy = true; + refy = RefY.Top; + valy = topStats.mean; + } + else if(sharedx && (gapsY.size() == 1 || gapsYStats.var < DefaultSharingStrategy.MAX_VARIANCE)) { + constraintY = Constraint.Spacing; + refy = RefY.Bottom; + + // In this case, give priority to center x alignement + if(centerXDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + refx = RefX.Center; + valx = centerXStats.mean; + } + } + else if(sharedx && bottomDistr.size() > 1 && bottomDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintY = Constraint.Distance; + refy = RefY.Bottom; + } + else if(sharedx && centerYDistr.size() > 1 && centerYDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintY = Constraint.Distance; + refy = RefY.Center; + } + else if(sharedx && topDistr.size() > 1 && topDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintY = Constraint.Distance; + refy = RefY.Top; + } + + if(gtype != Grouping.Type.Collection && sharedy && (gapsX.size() == 1 || gapsXStats.var < DefaultSharingStrategy.MAX_VARIANCE)) { + constraintX = Constraint.Spacing; + if(widthStats.var < DefaultSharingStrategy.MAX_VARIANCE) refx = RefX.Center; + else refx = RefX.Left; + } + + + if(gtype == Grouping.Type.Collection) valx -= dx; + + xReferenceStructure = new XReferenceStructure(refx, valx, sharedx, constraintX); + yReferenceStructure = new YReferenceStructure(refy, valy, sharedy, constraintY); + } + + /* + public ReferenceStructureCreator(ObservableList<Mark> marks, Grouping.Type gtype) { + + double valx = marks.get(0).getCoords().getX(), valy = marks.get(0).getCoords().getY(); + + if(marks.size() == 1) { + RefX refx = marks.get(0).getCoords().getXRef(); + RefY refy = marks.get(0).getCoords().getYRef(); + + if(!(marks.get(0) instanceof VisBody)) { + switch(refx) { + case Center: + valx = marks.get(0).centerX(); + break; + case Left: + valx = marks.get(0).left(); + break; + case Right: + valx = marks.get(0).right(); + break; + } + + switch(refy) { + case Center: + valy = marks.get(0).centerY(); + break; + case Top: + valy = marks.get(0).top(); + break; + case Bottom: + valy = marks.get(0).bottom(); + break; + } + } + + xReferenceStructure = new XReferenceStructure(refx, valx, false, Constraint.None); + yReferenceStructure = new YReferenceStructure(refy, valy, true, Constraint.None); + return; + } + + SortedList<Mark> marksX = new SortedList<>(marks, new XMarkComparator()); + SortedList<Mark> marksY = new SortedList<>(marks, new YMarkComparator()); + + ///////////////////////////////// + List<Double> left = getXLeft(marksX); + Stats leftStats = new Stats(left); + + List<Double> centerx = getXCenter(marksX); + Stats centerXStats = new Stats(centerx); + + List<Double> right = getXRight(marksX); + Stats rightStats = new Stats(right); + + List<Double> bottom = getYBottom(marksY); + Stats bottomStats = new Stats(bottom); + + List<Double> centery = getYCenter(marksY); + Stats centerYStats = new Stats(centery); + + List<Double> top = getYTop(marksY); + Stats topStats = new Stats(top); + + ///////////////////////////////// + List<Double> gapsX = getXGap(marksX); + Stats gapsXStats = new Stats(gapsX); + + List<Double> gapsY = getYGap(marksY); + Stats gapsYStats = new Stats(gapsY); + ///////////////////////////////// + + //////////////////////////////////// + List<Double> topDistr = getTopDistribution(marksY); + Stats topDistrStats = new Stats(topDistr); + + List<Double> bottomDistr = getBottomDistribution(marksY); + Stats bottomDistrStats = new Stats(bottomDistr); + + List<Double> centerYDistr = getCenterYDistribution(marksY); + Stats centerYDistrStats = new Stats(centerYDistr); + + List<Double> leftDistr = getLeftDistribution(marksX); + Stats leftDistrStats = new Stats(leftDistr); + + List<Double> rightDistr = getRightDistribution(marksX); + Stats rightDistrStats = new Stats(rightDistr); + + List<Double> centerXDistr = getCenterXDistribution(marksX); + Stats centerXDistrStats = new Stats(centerXDistr); + + List<Double> widthDistr = getWidths(marksX); + Stats widthStats = new Stats(widthDistr); + ////////////////////////////// + + Constraint constraintX = Constraint.None, constraintY = Constraint.None; + RefX refx = RefX.Center; + RefY refy = RefY.Center; + boolean sharedx = false, sharedy = false; + valx = leftStats.min; + valy = bottomStats.min; + + double dx = 0; // This is to add a gap on the x-axis based on the distribution constraint (for collections) + + if(leftStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedx = true; + refx = RefX.Left; + valx = leftStats.mean; + } + else if(centerXStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedx = true; + refx = RefX.Center; + valx = centerXStats.mean; + } + else if(rightStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedx = true; + refx = RefX.Right; + valx = rightStats.mean; + } + else if(centerXDistr.size() > 1 && centerXDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Distance; + refx = RefX.Center; + dx = centerXDistrStats.mean/8; + } + else if(leftDistr.size() > 1 && leftDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Distance; + refx = RefX.Left; + dx = leftDistrStats.mean/8; + } + else if(gapsX.size() > 1 && gapsXStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Spacing; + refx = RefX.Left; + } + else if(rightDistr.size() > 1 && rightDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintX = Constraint.Distance; + refx = RefX.Right; + dx = rightDistrStats.mean/8; + } + + ////////////// Y ############################ + if(bottomStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedy = true; + refy = RefY.Bottom; + valy = bottomStats.mean; + } + else if(centerYStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedy = true; + refy = RefY.Center; + valy = centerYStats.mean; + } + else if(topStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + sharedy = true; + refy = RefY.Top; + valy = topStats.mean; + } + else if(sharedx && (gapsY.size() == 1 || gapsYStats.var < DefaultSharingStrategy.MAX_VARIANCE)) { + constraintY = Constraint.Spacing; + refy = RefY.Bottom; + + // In this case, give priority to center x alignement + if(centerXDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + refx = RefX.Center; + valx = centerXStats.mean; + } + } + else if(sharedx && bottomDistr.size() > 1 && bottomDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintY = Constraint.Distance; + refy = RefY.Bottom; + } + else if(sharedx && centerYDistr.size() > 1 && centerYDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintY = Constraint.Distance; + refy = RefY.Center; + } + else if(sharedx && topDistr.size() > 1 && topDistrStats.var < DefaultSharingStrategy.MAX_VARIANCE) { + constraintY = Constraint.Distance; + refy = RefY.Top; + } + + if(gtype != Grouping.Type.Collection && sharedy && (gapsX.size() == 1 || gapsXStats.var < DefaultSharingStrategy.MAX_VARIANCE)) { + constraintX = Constraint.Spacing; + if(widthStats.var < DefaultSharingStrategy.MAX_VARIANCE) refx = RefX.Center; + else refx = RefX.Left; + } + + + if(gtype == Grouping.Type.Collection) valx -= dx; + + xReferenceStructure = new XReferenceStructure(refx, valx, sharedx, constraintX); + yReferenceStructure = new YReferenceStructure(refy, valy, sharedy, constraintY); + } + */ + + public XReferenceStructure getXReferenceStructure() { + return xReferenceStructure; + } + + public YReferenceStructure getYReferenceStructure() { + return yReferenceStructure; + } + + protected List<Double> getXLeft(List<Mark> marks){ + List<Double> left = new ArrayList<>(); + for(Mark mark: marks) { + if(mark instanceof VisBody) left.add(mark.getCoords().getX()); + else left.add(mark.left()); + } + return left; + } + + protected List<Double> getXRight(List<Mark> marks){ + List<Double> right = new ArrayList<>(); + for(Mark mark: marks) { + if(mark instanceof VisBody) right.add(mark.getCoords().getX()); + else right.add(mark.right()); + } + return right; + } + + protected List<Double> getXCenter(List<Mark> marks){ + List<Double> center = new ArrayList<>(); + for(Mark mark: marks) { + if(mark instanceof VisBody) center.add(mark.getCoords().getX()); + else center.add(mark.centerX()); + } + return center; + } + + protected List<Double> getYTop(List<Mark> marks){ + List<Double> top = new ArrayList<>(); + for(Mark mark: marks) { + if(mark instanceof VisBody) top.add(mark.getCoords().getY()); + else top.add(mark.top()); + } + return top; + } + + protected List<Double> getYCenter(List<Mark> marks){ + List<Double> center = new ArrayList<>(); + for(Mark mark: marks) { + if(mark instanceof VisBody) center.add(mark.getCoords().getY()); + else center.add(mark.centerY()); + } + return center; + } + + protected List<Double> getYBottom(List<Mark> marks){ + List<Double> bottom = new ArrayList<>(); + for(Mark mark: marks) { + if(mark instanceof VisBody) bottom.add(mark.getCoords().getY()); + else bottom.add(mark.bottom()); + } + return bottom ; + } + + + protected List<Double> getLeftDistribution(SortedList<Mark> marks){ + List<Double> dist = new ArrayList<>(); + if(marks.size() < 2) return dist; + + if(marks.get(0) instanceof VisBody) { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).getCoords().getX() - marks.get(i-1).getCoords().getX()); + } + } else { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).left() - marks.get(i-1).left()); + } + } + + return dist; + } + + protected List<Double> getCenterXDistribution(SortedList<Mark> marks){ + List<Double> dist = new ArrayList<>(); + if(marks.size() < 2) return dist; + + if(marks.get(0) instanceof VisBody) { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).getCoords().getX() - marks.get(i-1).getCoords().getX()); + } + } else { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).centerX() - marks.get(i-1).centerX()); + } + } + + return dist; + } + + protected List<Double> getRightDistribution(SortedList<Mark> marks){ + List<Double> dist = new ArrayList<>(); + if(marks.size() < 2) return dist; + + if(marks.get(0) instanceof VisBody) { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).getCoords().getX() - marks.get(i-1).getCoords().getX()); + } + } else { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).right() - marks.get(i-1).right()); + } + } + + return dist; + } + + protected List<Double> getBottomDistribution(SortedList<Mark> marks){ + List<Double> dist = new ArrayList<>(); + if(marks.size() < 2) return dist; + + if(marks.get(0) instanceof VisBody) { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).getCoords().getY() - marks.get(i-1).getCoords().getY()); + } + } else { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).bottom() - marks.get(i-1).bottom()); + } + } + + return dist; + } + + protected List<Double> getCenterYDistribution(SortedList<Mark> marks){ + List<Double> dist = new ArrayList<>(); + if(marks.size() < 2) return dist; + + if(marks.get(0) instanceof VisBody) { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).getCoords().getY() - marks.get(i-1).getCoords().getY()); + } + } else { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).centerY() - marks.get(i-1).centerY()); + } + } + + return dist; + } + + protected List<Double> getTopDistribution(SortedList<Mark> marks){ + List<Double> dist = new ArrayList<>(); + if(marks.size() < 2) return dist; + + if(marks.get(0) instanceof VisBody) { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).getCoords().getY() - marks.get(i-1).getCoords().getY()); + } + } else { + for(int i = 1; i < marks.size(); ++i) { + dist.add(marks.get(i).top() - marks.get(i-1).top()); + } + } + + return dist; + } + + protected List<Double> getXGap(SortedList<Mark> marks){ + List<Double> gap = new ArrayList<>(); + if(marks.size() < 2) return gap; + + for(int i = 1; i < marks.size(); ++i) { + double right = marks.get(i-1).right(); + double left = marks.get(i).left(); + gap.add(left - right); + } + + return gap; + } + + protected List<Double> getYGap(SortedList<Mark> marks){ + List<Double> gap = new ArrayList<>(); + if(marks.size() < 2) return gap; + + for(int i = 1; i < marks.size(); ++i) { + double top = marks.get(i-1).top(); + double bottom = marks.get(i).bottom(); + gap.add(bottom - top); + } + + return gap; + } + + protected List<Double> getWidths(SortedList<Mark> marks){ + List<Double> ws = new ArrayList<>(); + if(marks.size() < 2) return ws; + + for(int i = 1; i < marks.size(); ++i) { + double width = marks.get(i-1).width(); + ws.add(width); + } + + return ws; + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/SharingStrategy.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/SharingStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..bf194ee612c0a3308cdf055977981602b3beeb0c --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/SharingStrategy.java @@ -0,0 +1,34 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.utils.GroupBinding; +import javafx.collections.ObservableList; + + +public abstract class SharingStrategy { + + public void share(Collection<FlexibleListProperty> properties, SortedSet<PropertyName> common, SortedSet<PropertyName> variable) { + for(FlexibleListProperty column: properties) { + if(isVariable(column)) { + variable.add(new PropertyName(column.getName())); + } + else common.add(new PropertyName(column.getName())); + } + } + + protected abstract boolean isVariable(FlexibleListProperty column); + public abstract VisCollection createCollection(ObservableList<Mark> marks); + public abstract VisGroup createGroup(ObservableList<Mark> marks); + public abstract void share(Map<PropertyName, FlexibleListProperty> properties, TreeMap<PropertyName, ArrayList<GroupBinding>> bindings, + SortedSet<PropertyName> common, SortedSet<PropertyName> variable); +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/XReferenceStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/XReferenceStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..7606f3fa6c241f97b4e551150958378c88aa3825 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/XReferenceStructure.java @@ -0,0 +1,28 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefX; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; + +public class XReferenceStructure extends ReferenceStructure { + private RefX refx = null; + private double x; + + public XReferenceStructure (RefX refx, double x, boolean shared, Constraint constraint) { + super(shared, constraint); + this.refx = refx; + this.x = x; + } + + public RefX getRef() { + return refx; + } + + public double getRefValue() { + return x; + } + + public String toString() { + return "X: " + refx + ", " + x + " " + constraint; + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupings/YReferenceStructure.java b/src/fr/inria/structgraphics/ui/viscanvas/groupings/YReferenceStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..0b22fbbd903e5435d08952ffce8f3c7dea658a63 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupings/YReferenceStructure.java @@ -0,0 +1,28 @@ +package fr.inria.structgraphics.ui.viscanvas.groupings; + +import fr.inria.structgraphics.graphics.ReferenceCoords.RefY; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; + +public class YReferenceStructure extends ReferenceStructure { + private RefY refy; + private double y; + + public YReferenceStructure (RefY refy, double y, boolean shared, Constraint constraint) { + super(shared, constraint); + this.refy = refy; + this.y = y; + } + + public RefY getRef() { + return refy; + } + + public double getRefValue() { + return y; + } + + public String toString() { + return "Y: " + refy + ", " + y + " " + constraint; + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/Axis.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/Axis.java new file mode 100644 index 0000000000000000000000000000000000000000..13c0ca7b7adc1605d701ab4fc1aa0a037c974ef8 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/Axis.java @@ -0,0 +1,131 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.types.PropertyName; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.spreadsheet.MathTransformation; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.shape.Line; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; + +public abstract class Axis extends Group implements ChangeListener<String>/*implements InvalidationListener*/ { + + protected Font textFont = new Font(10); + protected Font labelFont = Font.font("Verdana", FontWeight.BOLD, 11); + //protected Color stroke = Color.GREY; + //protected Color textColor = Color.GREY; + protected double axisStrokeWidth = 0.8; + protected double tickStrokeWidth = 0.6; + protected double ticLength = 6; + protected double labelGap = 1; + + protected VisCollection group; + protected DataVariable variable; + + protected Line axisLine; + protected Group tics; + protected Group values; + protected Text label; + + protected double min, max; + protected double minValue, maxValue; + + public Axis(VisCollection group) { + this.group = group; + setVisible(false); + + axisLine = new Line(); + tics = new Group(); + values = new Group(); + label = new Text(); + label.setFont(labelFont); + + label.fillProperty().bind(((VisFrame)group.getRoot()).stroke); + axisLine.strokeProperty().bind(((VisFrame)group.getRoot()).stroke);// setStroke(stroke); + axisLine.setStrokeWidth(axisStrokeWidth); + + getChildren().add(axisLine); + getChildren().add(tics); + getChildren().add(values); + getChildren().add(label); + } + + public void createCopy(Axis axis) { + axis.variable = variable; + axis.setVisible(isVisible()); + axis.update(); + } + + public void showVariable(DataVariable variable) { + if(variable != this.variable && this.variable != null) { + this.variable.scaleShownProperty.set(false); + this.variable.transformationProperty().get().getExpression().removeListener(this); + } + this.variable = variable; + + if(!variable.scaleShownProperty.get()) { + setVisible(true); + update(); + } + else setVisible(false); + + variable.transformationProperty().get().getExpression().addListener(this); + } + + public void showVariableOnParent(DataVariable variable) { + if(variable != this.variable && this.variable != null) { + this.variable.scaleShownPropertyOuter.set(false); + this.variable.transformationProperty().get().getExpression().removeListener(this); + } + this.variable = variable; + + if(!variable.scaleShownPropertyOuter.get()) { + setVisible(true); + update(); + } + else setVisible(false); + + variable.transformationProperty().get().getExpression().addListener(this); + } + + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + update(); + } + + protected double transform(Object val) { + return ((MathTransformation)variable.transformationProperty().get()).transform(val); + } + + protected double invTransform(double val) { + return ((MathTransformation)variable.transformationProperty().get()).inverseTransform(val); + } +/* + @Override + public void invalidated(Observable observable) { + update(); + } +*/ + public void update() { + if(!isVisible()) return; + updateTicksAndValues(); + updateLine(); + updateLabel(); + } + + + protected FlexibleListProperty getValues() { + return group.getChildPropertyStructure().getProperty(new PropertyName(variable.getPropertyName())).flattenFlex(); + } + + protected abstract void updateLine(); + protected abstract void updateTicksAndValues(); + protected abstract void updateLabel(); +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/AxisX.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/AxisX.java new file mode 100644 index 0000000000000000000000000000000000000000..f4b46ca837f1171b9b3f15fc21749330fcebe571 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/AxisX.java @@ -0,0 +1,168 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.ui.spreadsheet.DataTransformation; +import fr.inria.structgraphics.ui.spreadsheet.DiscreteTransformation; +import fr.inria.structgraphics.ui.spreadsheet.MappingProperty; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable.DataType; +import fr.inria.structgraphics.ui.utils.Ticker; +import javafx.beans.property.Property; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.shape.Line; +import javafx.scene.text.Text; + +public class AxisX extends Axis { + + private double offset = 0; + + public AxisX(VisCollection group) { + super(group); + } + + @Override + protected void updateLine() { + axisLine.startYProperty().set(group.getInteractor().getXAxis().startYProperty().get()); + axisLine.endYProperty().set(group.getInteractor().getXAxis().endYProperty().get()); + axisLine.startXProperty().set(min); + axisLine.endXProperty().set(max); + } + + @Override + protected void updateTicksAndValues() { + values.getChildren().clear(); + tics.getChildren().clear(); + offset = 0; + + List<Double> positions, values = null; + double endY = axisLine.getStartY() + ticLength; + + if(variable.getType().equals(DataType.Functional)) { + minValue = 0; + maxValue = 0; + + if(variable.isX()) { // This is to show the axis only along the active y values + FlexibleListProperty propValues = getValues(); + for(Property prop: propValues) { + if((double)prop.getValue() < minValue) minValue = (double)prop.getValue(); + if((double)prop.getValue() > maxValue) maxValue = (double)prop.getValue(); + } + } else { + minValue = group.getInteractor().left.get(); + maxValue = group.getInteractor().right.get(); + } + + + double min = transform(minValue); + double max = transform(maxValue); + + //double min = transform(group.getInteractor().left.get()); + //double max = transform(group.getInteractor().right.get()); + + Ticker ticker = new Ticker(min, max, 10, transform(new Double(20)) - transform(new Double(0))); + positions = ticker.getTicks(); + + for(int i = 0; i < positions.size(); ++i) { + double val = positions.get(i); + double x = invTransform(val); + + if(i == 0) this.min = x; + else if(i == positions.size() - 1) this.max = x; + + Line line = new Line(x, axisLine.getStartY(), x, endY); + line.strokeProperty().bind(((VisFrame)group.getRoot()).stroke);// setStroke(stroke); + + //line.setStroke(stroke); + line.setStrokeWidth(tickStrokeWidth); + tics.getChildren().add(line); + + if(values != null) updateValue(x, line.getEndY(), values.get(i)); + else updateValue(x, line.getEndY(), x); + } + + } else { + minValue = group.getInteractor().left.get(); + maxValue = group.getInteractor().right.get(); + + this.min = group.getInteractor().getXAxis().startXProperty().get(); + this.max = group.getInteractor().getXAxis().endXProperty().get(); + + positions = new ArrayList<>(); + values = new ArrayList<>(); + + DiscreteTransformation transformation = (DiscreteTransformation)variable.transformationProperty().get(); + TreeMap<MappingProperty, StringProperty> map = transformation.getMap(); + for(MappingProperty prop:map.keySet()) { + double dx = 0; + values.add((double)prop.get()); + positions.add((double)prop.get() + dx); + } + + /* + List<Mark> marks = group.getComponents(); + for(Mark mark : marks) { + double dx = 0; + values.add(mark.getCoords().x.get()); + positions.add(mark.getCoords().x.get() + dx); + }*/ + + for(int i = 0; i < positions.size(); ++i) { + double x = positions.get(i); + Line line = new Line(x, axisLine.getStartY(), x, endY); + line.strokeProperty().bind(((VisFrame)group.getRoot()).stroke);// setStroke(stroke); +// line.setStroke(stroke); + line.setStrokeWidth(tickStrokeWidth); + tics.getChildren().add(line); + + if(values != null) updateValue(x, line.getEndY(), values.get(i)); + else updateValue(x, line.getEndY(), x); + } + } + + + } + + protected void updateValue(double posx, double posy, double xvalue) { + double dy = 0; + if(variable.getType().equals(DataType.Symbolic)) { + dy = group.coords.getY() - group.bottom(); + } + + String value = variable.transformationProperty().get().toData(xvalue); + + Text label = new Text(value); + label.setFont(textFont); + //label.setFill(textColor); + label.fillProperty().bind(((VisFrame)group.getRoot()).stroke);// setStroke(stroke); + + label.xProperty().set(posx - label.getLayoutBounds().getWidth()/2); + + label.yProperty().set(posy + label.getLayoutBounds().getHeight() + labelGap + dy); + offset = Math.max(offset, label.yProperty().get()); + + values.getChildren().add(label); + } + + @Override + protected void updateLabel() { + label.textProperty().unbind(); + label.textProperty().bind(variable.getName(0)); + + //double min = group.getInteractor().left.get(); + //double max = group.getInteractor().right.get(); + double centerx = (maxValue + minValue)/2; + + label.xProperty().set(centerx - label.getLayoutBounds().getWidth()/2); + label.yProperty().set(offset + label.getLayoutBounds().getHeight() + labelGap); + } + + +} + diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/AxisY.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/AxisY.java new file mode 100644 index 0000000000000000000000000000000000000000..5bb34f55c7880624b94e0cba98c63e075cbe1b73 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/AxisY.java @@ -0,0 +1,162 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.ArrayList; +import java.util.List; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.types.FlexibleListProperty; +import fr.inria.structgraphics.ui.spreadsheet.MathTransformation; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable.DataType; +import fr.inria.structgraphics.ui.utils.Ticker; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.Property; +import javafx.scene.shape.Line; +import javafx.scene.text.Text; +import javafx.scene.transform.Rotate; + +public class AxisY extends Axis { + + private double offset = 0; + + public AxisY(VisCollection group) { + super(group); + + Rotate rotation = new Rotate(); + rotation.pivotXProperty().set(0); + rotation.pivotYProperty().set(0); + rotation.setAngle(-90); + label.getTransforms().add(rotation); + } + + @Override + protected void updateLine() { + axisLine.startYProperty().set(min); + axisLine.endYProperty().set(max); + + axisLine.startXProperty().set(group.getInteractor().getYAxis().startXProperty().get()); + axisLine.endXProperty().set(group.getInteractor().getYAxis().endXProperty().get()); + } + + @Override + protected void updateTicksAndValues() { + values.getChildren().clear(); + tics.getChildren().clear(); + offset = 0; + + List<Double> positions, values = null; + double endX = axisLine.getStartX() - ticLength; + + if(variable.getType().equals(DataType.Functional)) { + minValue = 0; + maxValue = 0; + + if(variable.isY()) { // This is to show the axis only along the active y values + FlexibleListProperty propValues = getValues(); + for(Property prop: propValues) { + if((double)prop.getValue() < minValue) minValue = (double)prop.getValue(); + if((double)prop.getValue() > maxValue) maxValue = (double)prop.getValue(); + } + } else { + minValue = -group.getInteractor().bottom.get(); + maxValue = -group.getInteractor().top.get(); + } + + double min = transform(minValue); + double max = transform(maxValue); + + //double min = transform(-group.getInteractor().bottom.get()); + //double max = transform(-group.getInteractor().top.get()); + + Ticker ticker = new Ticker(min, max, 10, transform(new Double(15)) - transform(new Double(0))); + positions = ticker.getTicks(); + + for(int i = 0; i < positions.size(); ++i) { + double val = positions.get(i); + double y = invTransform(val); + + if(i == 0) this.min = -y; + else if(i == positions.size() - 1) this.max = -y; + + Line line = new Line(axisLine.getStartX(), -y, endX, -y); + line.strokeProperty().bind(((VisFrame)group.getRoot()).stroke); + //line.setStroke(stroke); + line.setStrokeWidth(tickStrokeWidth); + tics.getChildren().add(line); + + if(values != null) updateValue(-y, line.getEndX(), -values.get(i)); + else updateValue(-y, line.getEndX(), -y); + } + } else { + minValue = -group.getInteractor().bottom.get(); + maxValue = -group.getInteractor().top.get(); + + this.min = group.getInteractor().getYAxis().startYProperty().get(); + this.max = group.getInteractor().getYAxis().endYProperty().get(); + + positions = new ArrayList<>(); + values = new ArrayList<>(); + List<Mark> marks = group.getComponents(); + for(Mark mark : marks) { + double dy = 0; + values.add(mark.getCoords().y.get()); + positions.add(mark.getCoords().y.get() + dy); + } + + for(int i = 0; i < positions.size(); ++i) { + double y = positions.get(i); + Line line = new Line(axisLine.getStartX(), -y, endX, -y); + line.strokeProperty().bind(((VisFrame)group.getRoot()).stroke); + //line.setStroke(stroke); + line.setStrokeWidth(tickStrokeWidth); + tics.getChildren().add(line); + + if(values != null) updateValue(-y, line.getEndX(), -values.get(i)); + else updateValue(-y, line.getEndX(), -y); + } + } + } + + + /* + protected double getInverseTransformed(double val) { + return new Double(variable.transformationProperty().get().toData(val)); + }*/ + + protected void updateValue(double posy, double posx, double yvalue) { + String value = variable.transformationProperty().get().toData(yvalue == 0? 0 : -yvalue); + + Text label = new Text(value); + label.setFont(textFont); +// label.setFill(textColor); + label.fillProperty().bind(((VisFrame)group.getRoot()).stroke); + + label.yProperty().set(posy + label.getLayoutBounds().getHeight()/4); + label.xProperty().set(posx - label.getLayoutBounds().getWidth() - labelGap); + offset = Math.min(offset, label.xProperty().get()); + + values.getChildren().add(label); + } + + + InvalidationListener labelListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + + } + }; + + + @Override + protected void updateLabel() { + label.textProperty().bind(variable.getName(0)); + + double centery = -(maxValue + minValue)/2; + + label.xProperty().set(-centery - label.getLayoutBounds().getWidth()/2); + label.yProperty().set(offset - label.getLayoutBounds().getHeight() - labelGap); + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/BodyInteractor.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/BodyInteractor.java new file mode 100644 index 0000000000000000000000000000000000000000..76459f70a05fcfbca21dca00fe888b76144765be --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/BodyInteractor.java @@ -0,0 +1,88 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.ComplexShape; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.utils.Cloner; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; + +public abstract class BodyInteractor extends ComplexShape { + + protected final static Color COL = new Color(Color.DEEPPINK.getRed(), Color.DEEPPINK.getGreen(), Color.DEEPPINK.getBlue(), 0.6);// Color.DEEPPINK; + protected final static Color COL2 = new Color(Color.CORNFLOWERBLUE.getRed(), Color.CORNFLOWERBLUE.getGreen(), Color.CORNFLOWERBLUE.getBlue(), 0.6);// Color.DEEPPINK; + + protected final static Color COL3 = new Color(Color.GOLDENROD.getRed(), Color.GOLDENROD.getGreen(), Color.GOLDENROD.getBlue(), 0.6); + + protected Circle handler; + public DoubleProperty left = new SimpleDoubleProperty(), right = new SimpleDoubleProperty(100), top = new SimpleDoubleProperty(-100), bottom = new SimpleDoubleProperty(); + + protected VisBody group; + + public BodyInteractor(VisBody group) { + this.group = group; + drawHandler(); + drawBoundary(); + } + + protected void drawHandler() { + handler = new Circle(0, 0, group.isExternal() ? 10 : 6); + + getChildren().add(handler); + handler.setFill(new Color(1, 1, 1, 0.1)); + handler.setStroke(group.isExternal() ? COL : COL2); + + addToGhost(Cloner.clone(handler)); + } + + protected abstract void drawBoundary(); + + public boolean contains(Circle trace) { + return handler.contains(trace.getCenterX(), trace.getCenterY()); + } + + public boolean intersectsCenter(Shape trace) { + if(trace == null || !handler.isVisible()) return false; + + Shape intersection = Shape.intersect((Shape) handler, trace); + if(!(intersection instanceof Path) || !((Path)intersection).getElements().isEmpty()) return true; + + return false; + } + + @Override + public boolean intersects(Shape trace) { + if(handler.isVisible()) return super.intersects(trace); + else return false; + } + + public void redraw() { + clear(); + drawHandler(); + drawBoundary(); + refresh(); + } + + public void refresh() { + ghost.getChildren().clear(); + addToGhost(Cloner.clone(handler)); + } + + + public double getXStart() { + return handler.getCenterX(); + } + + public double getYStart() { + return handler.getCenterY(); + } + + public abstract Group createCopy(double dx, double dy); + + public abstract double getWidth(); + public abstract double getHeight(); +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/CollectionInteractor.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/CollectionInteractor.java new file mode 100644 index 0000000000000000000000000000000000000000..736d84325c01d991aac51656b5cacfba3b6ff5d4 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/CollectionInteractor.java @@ -0,0 +1,119 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.types.AlignmentProperty; +import fr.inria.structgraphics.types.AlignmentXProperty; +import fr.inria.structgraphics.ui.utils.Cloner; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; + +public class CollectionInteractor extends BodyInteractor { + + private Line xAxis, yAxis; + + public CollectionInteractor(VisCollection collection) { + super(collection); + } + + protected boolean isXNeeded() { + if(group.isExternal()) return true; + else { + Container container = group.getContainer(); + if(container instanceof VisCollection && ((VisCollection)container).getAlignYProperty().get() == AlignmentProperty.XSticky.Yes) + return false; + + return true; + } + } + + protected boolean isYNeeded() { + if(group.isExternal()) return true; + else { + Container container = group.getContainer(); + if(container instanceof VisCollection && ((VisCollection)container).getAlignXProperty().get() == AlignmentProperty.YSticky.Yes) + return false; + + return true; + } + } + + @Override + protected void drawBoundary() { + xAxis = new Line(0, 0, 100, 0); + xAxis.startXProperty().bind(left); + xAxis.endXProperty().bind(right); + getChildren().add(xAxis); + xAxis.setStroke(group.isExternal() ? COL : COL2); + if(!group.isExternal()) xAxis.getStrokeDashArray().addAll(2d); + else xAxis.getStrokeDashArray().clear(); + + yAxis = new Line(0, 0, 0, -100); + yAxis.startYProperty().bind(bottom); + yAxis.endYProperty().bind(top); + getChildren().add(yAxis); + yAxis.setStroke(group.isExternal() ? COL : COL2); + if(!group.isExternal()) yAxis.getStrokeDashArray().addAll(2d); + else yAxis.getStrokeDashArray().clear(); + + addToGhost(Cloner.clone(xAxis)); + addToGhost(Cloner.clone(yAxis)); + } + + public Line getXAxis() { + return xAxis; + } + + public Line getYAxis() { + return yAxis; + } + + @Override + public void refresh() { + super.refresh(); + addToGhost(Cloner.clone(xAxis)); + addToGhost(Cloner.clone(yAxis)); + + boolean isX = isXNeeded(); + boolean isY = isYNeeded(); + + xAxis.setVisible(isX); + yAxis.setVisible(isY); + handler.setVisible(isX || isY); + } + + @Override + public void setHighlight(boolean highlight) { + super.setHighlight(highlight && handler.isVisible()); + } + + @Override + public Group createCopy(double dx, double dy) { + Group group = new Group(); + Circle handler_ = new Circle(dx, dy, 10); + Line xAxis_ = new Line(xAxis.getStartX() + dx, xAxis.getStartY() + dy, xAxis.getEndX() + dx, xAxis.getEndY() + dy); + Line yAxis_ = new Line(yAxis.getStartX() + dx, yAxis.getStartY() + dy, yAxis.getEndX() + dx, yAxis.getEndY() + dy); + + group.getChildren().add(handler_); + group.getChildren().add(xAxis_); + group.getChildren().add(yAxis_); + + return group; + } + + @Override + public double getWidth() { + return Math.abs(xAxis.getEndX() - xAxis.getStartX()); + } + + @Override + public double getHeight() { + return Math.abs(yAxis.getEndY() - yAxis.getStartY()); + } +} + diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintController.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintController.java new file mode 100644 index 0000000000000000000000000000000000000000..b201ed04ad9731290d4c1cd7b8fa3522421e6bf2 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintController.java @@ -0,0 +1,76 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.DistributionProperty; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; + +public abstract class ConstraintController extends Group { + + public enum ConstraintHandle { + LEFT, RIGHT, UP, DOWN + } + + protected DistributionProperty.Constraint type = Constraint.None; + protected VisBody group; + + protected double distance_init; + protected Mark activeMark = null; + + protected static boolean handlerhold = false; // A constraint handler is currently hold/dragged + protected static int xselectionIndex = 0, yselectionIndex = 0; + protected static double xselectionPos = Double.MAX_VALUE, yselectionPos = Double.MAX_VALUE; + + protected static VisBody highlighted_group = null; + + public ConstraintController(VisBody group) { + this.group = group; + } + + public abstract void updateMarkPositions(Mark mark); + public abstract void setMark(Mark mark, boolean highlight); + + + public ConstraintController getSelf() { + return this; + } + + public boolean intersects(Circle trace) { + if(!isVisible()) return false; + + if(trace == null) return false; + + for(Node shape: getChildren()) { + if(shape instanceof Circle && shape.isVisible()) { + Shape intersection = Shape.intersect((Circle) shape, trace); + if(!(intersection instanceof Path) || !((Path)intersection).getElements().isEmpty()) { + + selectHandle((Circle)shape); + + return true; + } + } + } + return false; + } + + protected void selectHandle(Circle shape) { + ConstraintController.handlerhold = true; + } + + public void unselectHandle() { + if(activeMark != null) activeMark.setConstraintControl(null, null); + ConstraintController.handlerhold = false; + } + + + public abstract void refresh(); + + public abstract void translate(Line lineTrace, ConstraintHandle constraintHandler); +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintXController.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintXController.java new file mode 100644 index 0000000000000000000000000000000000000000..5742acf5e4888430d78c82c2aae2283d0d58b9e5 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintXController.java @@ -0,0 +1,299 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.List; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.Path; +import javafx.scene.shape.Shape; + +public class ConstraintXController extends ConstraintController { + + private int dy = 10; + + protected Line leftline, rightline; + protected Circle leftcircle, rightcircle; + + public ConstraintXController(VisBody group) { + super(group); + + leftline = new Line(); + rightline = new Line(); + leftcircle = new Circle(); + rightcircle = new Circle(); + leftcircle.radiusProperty().set(4); + rightcircle.radiusProperty().set(4); + + leftline.strokeProperty().set(Color.RED); + rightline.strokeProperty().set(Color.RED); + leftcircle.fillProperty().set(Color.RED); + rightcircle.fillProperty().set(Color.RED); + + getChildren().add(leftline); + getChildren().add(rightline); + getChildren().add(leftcircle); + getChildren().add(rightcircle); + + group.getConstraintXProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + type = group.getConstraintXProperty().get(); + + if(type != Constraint.None && !group.getComponents().isEmpty()) { + if(group.getConstraintYProperty().get() == Constraint.None) { + group.reorderChildren(true); + } + refresh(); + updateRight(0); + } else refresh(); + } + }); + + setVisible(false); + + group.distanceXProperty.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if(group.getComponents().isEmpty()) return; + + Mark mark = group.getComponents().get(Math.min(ConstraintController.xselectionIndex, group.getComponents().size() - 1)); + activeMark = mark; + group.invalidated(mark.getCoords().x); + group.updateLayout(mark); + } + }); + } + + + @Override + public void translate(Line lineTrace, ConstraintHandle constraintHandler) { + + if(constraintHandler == ConstraintHandle.LEFT) { + double dx = lineTrace.getStartX() - lineTrace.getEndX(); +// TODO : It does not find the correct Handler!!! That's all!!! + group.distanceXProperty.set(distance_init + dx); + //group.invalidated(activeMark.getCoords().x); + } + else { + double dx = lineTrace.getEndX() - lineTrace.getStartX(); + group.distanceXProperty.set(distance_init + dx); + //group.invalidated(activeMark.getCoords().x); + } + } + + + public void refresh() { + if(type == Constraint.None) return; + List<Mark> marks = group.getComponents(); + + if(marks.size() == 1) { + double width = marks.get(0).width(); + if(type == Constraint.Spacing) group.distanceXProperty.set(0); + else group.distanceXProperty.set(width + 10); // TODO: This is arbitrary!!! + } else if(marks.size() > 1) { + if(type == Constraint.Spacing) { + group.distanceXProperty.set(marks.get(1).left() - marks.get(0).right()); + } else { + group.distanceXProperty.set(marks.get(1).getCoords().getX() - marks.get(0).getCoords().getX()); + } + } + } + + @Override + public void setMark(Mark mark, boolean highlight) { + if(type == Constraint.None) { + setVisible(false); + return; + } + + + if(highlight) { + activeMark = mark; + int index = group.getComponents().indexOf(mark); + + double ypos = dy + group.getControlYPosition(); + + if(index > 0) { + if(type == Constraint.Spacing) { + double left = group.getComponents().get(index).left(); + + leftcircle.centerXProperty().set(left - group.distanceXProperty.get()); + leftcircle.centerYProperty().set(ypos); + + leftline.startXProperty().set(left - group.distanceXProperty.get()); + leftline.startYProperty().set(ypos); + leftline.endXProperty().set(left); + leftline.endYProperty().set(ypos); + } + else { + double x = group.getComponents().get(index).getCoords().getX(); + + leftcircle.centerXProperty().set(x - group.distanceXProperty.get()); + leftcircle.centerYProperty().set(ypos); + + leftline.startXProperty().set(x - group.distanceXProperty.get()); + leftline.startYProperty().set(ypos); + leftline.endXProperty().set(x); + leftline.endYProperty().set(ypos); + } + + leftline.setVisible(true); + leftcircle.setVisible(true); + } else { + leftcircle.centerXProperty().set(-1000); + leftline.setVisible(false); + leftcircle.setVisible(false); + } + + if(index < group.getComponents().size() - 1) { + if(type == Constraint.Spacing) { + double right = group.getComponents().get(index).right(); + + rightline.startXProperty().set(right); + rightline.startYProperty().set(ypos); + rightline.endXProperty().set(right + group.distanceXProperty.get()); + rightline.endYProperty().set(ypos); + + rightcircle.centerXProperty().set(right + group.distanceXProperty.get()); + rightcircle.centerYProperty().set(ypos); + } + else { + double x = group.getComponents().get(index).getCoords().getX(); + + rightline.startXProperty().set(x); + rightline.startYProperty().set(ypos); + rightline.endXProperty().set(x + group.distanceXProperty.get()); + rightline.endYProperty().set(ypos); + + rightcircle.centerXProperty().set(x + group.distanceXProperty.get()); + rightcircle.centerYProperty().set(ypos); + } + + rightline.setVisible(true); + rightcircle.setVisible(true); + } else { + rightcircle.centerXProperty().set(100000); + rightline.setVisible(false); + rightcircle.setVisible(false); + } + + setVisible(true); + } + else { + activeMark = null; + setVisible(false); + flagSelection(); + } + } + + @Override + public void updateMarkPositions(Mark mark) { + if(type == Constraint.None) { + return; + } + + int index = group.getComponents().indexOf(mark); + + updateLeft(index); + updateRight(index); + } + + + private void updateLeft(int index) { + Mark mark = group.getComponents().get(index); + if(mark == null) return; + + for(int i = index - 1; i >=0; --i) { + Mark mark_ = group.getComponents().get(i); + if(type == Constraint.Spacing) { + if(mark_ instanceof VisBody) { + double dx = mark_.getCoords().getX() - mark_.left(); + mark_.setPositionX(mark.left() - group.distanceXProperty.get() - mark_.width() + dx); + } + else { + switch(mark_.getCoords().getXRef()) { + case Center: mark_.setPositionX(mark.left() - group.distanceXProperty.get() - mark_.width()/2); + break; + case Left: mark_.setPositionX(mark.left() - group.distanceXProperty.get() - mark_.width()); + break; + case Right: mark_.setPositionX(mark.left() - group.distanceXProperty.get()); + break; + } + } + } + else { + if(mark_ != null) mark_.setPositionX(mark.getCoords().getX() - group.distanceXProperty.get()); + } + mark = mark_; + } + } + + private void updateRight(int index) { + if(group.getComponents().size() - 1 < index) return; + + Mark mark = group.getComponents().get(index); + for(int i = index + 1; i < group.getComponents().size(); ++i) { + Mark mark_ = group.getComponents().get(i); + if(type == Constraint.Spacing) { + if(mark_ instanceof VisBody) { + double dx = mark_.getCoords().getX() - mark_.left(); + mark_.setPositionX(mark.right() + group.distanceXProperty.get() + dx); + } + else { + switch(mark_.getCoords().getXRef()) { + case Center: mark_.setPositionX(mark.right() + group.distanceXProperty.get() + mark_.width()/2); + break; + case Left: mark_.setPositionX(mark.right() + group.distanceXProperty.get()); + break; + case Right: mark_.setPositionX(mark.right() + group.distanceXProperty.get() + mark_.width()); + break; + } + } + } + else { + mark_.setPositionX(mark.getCoords().getX() + group.distanceXProperty.get()); + } + mark = mark_; + } + } + + + @Override + protected void selectHandle(Circle shape) { + super.selectHandle(shape); + + if(shape == leftcircle) { + distance_init = group.distanceXProperty.doubleValue(); + activeMark.setConstraintControl(getSelf(), ConstraintHandle.LEFT); + flagSelection(); + } + else { + distance_init = group.distanceXProperty.doubleValue(); + activeMark.setConstraintControl(getSelf(), ConstraintHandle.RIGHT); + flagSelection(); + } + } + + protected void flagSelection() { + if(!ConstraintController.handlerhold && activeMark == null) { + xselectionIndex = 0; + xselectionPos = Double.MAX_VALUE; + } + else if(activeMark != null) { + xselectionIndex = group.getComponents().indexOf(activeMark); + xselectionPos = activeMark.getCoords().getX(); + } + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintYController.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintYController.java new file mode 100644 index 0000000000000000000000000000000000000000..60b1791fab6db84e9b18016feb9537b15eeec8ba --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/ConstraintYController.java @@ -0,0 +1,322 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.List; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.types.DistributionProperty.Constraint; +import fr.inria.structgraphics.ui.viscanvas.groupinteractors.ConstraintController.ConstraintHandle; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; + +public class ConstraintYController extends ConstraintController { + + private int dx = -10; + + protected Line bottomline, topline; + protected Circle bottomcircle, topcircle; + + public ConstraintYController(VisBody group) { + super(group); + + bottomline = new Line(); + topline = new Line(); + bottomcircle = new Circle(); + topcircle = new Circle(); + bottomcircle.radiusProperty().set(4); + topcircle.radiusProperty().set(4); + + bottomline.strokeProperty().set(Color.RED); + topline.strokeProperty().set(Color.RED); + bottomcircle.fillProperty().set(Color.RED); + topcircle.fillProperty().set(Color.RED); + + getChildren().add(bottomline); + getChildren().add(topline); + getChildren().add(bottomcircle); + getChildren().add(topcircle); + + group.getConstraintYProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + type = group.getConstraintYProperty().get(); + + if(type != Constraint.None && !group.getComponents().isEmpty()) { + if(group.getConstraintXProperty().get() == Constraint.None) { + group.reorderChildren(false); + } + refresh(); + updateTop(0); + } else refresh(); + } + }); + + setVisible(false); + + group.distanceYProperty.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if(group.getComponents().isEmpty()) return; + + Mark mark = group.getComponents().get(Math.min(ConstraintController.yselectionIndex, group.getComponents().size() - 1)); + activeMark = mark; + group.invalidated(mark.getCoords().y); + group.updateLayout(mark); + } + }); + } + + + @Override + public void translate(Line lineTrace, ConstraintHandle constraintHandler) { + + if(constraintHandler == ConstraintHandle.UP) { + double dy = lineTrace.getStartY() - lineTrace.getEndY(); + + group.distanceYProperty.set(distance_init + dy); + //group.invalidated(activeMark.getCoords().y); + } + else { + double dy = lineTrace.getEndY() - lineTrace.getStartY(); + + group.distanceYProperty.set(distance_init + dy); + //group.invalidated(activeMark.getCoords().y); + } + } + + + @Override + public void refresh() { + if(type == Constraint.None) return; + List<Mark> marks = group.getComponents(); + + if(marks.size() == 1) { + //double height = marks.get(0).height(); + //if(type == Constraint.Spacing) group.distanceYProperty.set(0); + //else group.distanceYProperty.set(height + 10); // TODO: This is arbitrary!!! + } else if(marks.size() > 1) { + if(type == Constraint.Spacing) { + group.distanceYProperty.set(marks.get(1).bottom() - marks.get(0).top()); + } else { + group.distanceYProperty.set(marks.get(1).getCoords().getY() - marks.get(0).getCoords().getY()); + } + } + + } + + @Override + public void setMark(Mark mark, boolean highlight) { + if(type == Constraint.None) { + setVisible(false); + return; + } + + if(highlight) { + activeMark = mark; + int index = group.getComponents().indexOf(mark); + + //ConstraintController.selectionIndex = index; + //System.err.println(activeMark + " ^^ " + selectionIndex); + + double xpos = dx + group.getControlXPosition(); + + if(index > 0) { + if(type == Constraint.Spacing) { + double bottom = group.getComponents().get(index).bottom(); + + bottomcircle.centerYProperty().set(-(bottom - group.distanceYProperty.get())); + bottomcircle.centerXProperty().set(xpos); + + bottomline.startYProperty().set(-(bottom - group.distanceYProperty.get())); + bottomline.startXProperty().set(xpos); + bottomline.endYProperty().set(-bottom); + bottomline.endXProperty().set(xpos); + } + else { + double y = group.getComponents().get(index).getCoords().getY(); + + bottomcircle.centerYProperty().set(-(y - group.distanceYProperty.get())); + bottomcircle.centerXProperty().set(xpos); + + bottomline.startYProperty().set(-(y - group.distanceYProperty.get())); + bottomline.startXProperty().set(xpos); + bottomline.endYProperty().set(-y); + bottomline.endXProperty().set(xpos); + } + + bottomline.setVisible(true); + bottomcircle.setVisible(true); + } else { + bottomcircle.centerYProperty().set(-20000); + bottomline.setVisible(false); + bottomcircle.setVisible(false); + } + + if(index < group.getComponents().size() - 1) { + if(type == Constraint.Spacing) { + double top = group.getComponents().get(index).top(); + + topline.startYProperty().set(-top); + topline.startXProperty().set(xpos); + topline.endYProperty().set(-(top + group.distanceYProperty.get())); + topline.endXProperty().set(xpos); + + topcircle.centerYProperty().set(-(top + group.distanceYProperty.get())); + topcircle.centerXProperty().set(xpos); + } + else { + double y = group.getComponents().get(index).getCoords().getY(); + + topline.startYProperty().set(-y); + topline.startXProperty().set(xpos); + topline.endYProperty().set(-(y + group.distanceYProperty.get())); + topline.endXProperty().set(xpos); + + topcircle.centerYProperty().set(-(y + group.distanceYProperty.get())); + topcircle.centerXProperty().set(xpos); + } + + topline.setVisible(true); + topcircle.setVisible(true); + } else { + topcircle.centerYProperty().set(20000); + topline.setVisible(false); + topcircle.setVisible(false); + } + + setVisible(true); + } + else { + activeMark = null; + setVisible(false); + flagSelection(); + } + } + + @Override + public void updateMarkPositions(Mark mark) { + if(type == Constraint.None) return; + int index = group.getComponents().indexOf(mark); +/* + int index; + if(mark.isHighlighted()) { + highlighted_group = group; + index = group.getComponents().indexOf(activeMark); + yselectionIndex = index; + yselectionPos = activeMark.getCoords().getY(); + } else if(highlighted_group == group) { + return; + } + else index = getClosest(yselectionPos);*/ + + updateBottom(index); + updateTop(index); + } + + /* + private int getClosest(double y) { + double epsilon = 10; + double dist = Double.MAX_VALUE; + int index = -1; + + for(int i = 0; i < group.getComponents().size(); ++i) { + Mark mark = group.getComponentAt(i); + double d = Math.abs(mark.getCoords().getY() - y); + if(d < dist) { + index = i; + dist = d; + } + } + + if(dist < epsilon) return index; + else return Math.min(yselectionIndex, group.getComponents().size() - 1); + }*/ + + private void updateBottom(int index) { + Mark mark = group.getComponents().get(index); + for(int i = index - 1; i >=0; --i) { + Mark mark_ = group.getComponents().get(i); + if(type == Constraint.Spacing) { + if(mark_ instanceof VisBody) { + double dy = mark_.getCoords().getY() - mark_.bottom(); + mark_.setPositionY(mark.bottom() - group.distanceYProperty.get() - mark_.height() + dy); + } + else { + switch(mark_.getCoords().getYRef()) { + case Center: mark_.setPositionY(mark.bottom() - group.distanceYProperty.get() - mark_.height()/2); + break; + case Bottom: mark_.setPositionY(mark.bottom() - group.distanceYProperty.get() - mark_.height()); + break; + case Top: mark_.setPositionY(mark.bottom() - group.distanceYProperty.get()); + break; + } + } + } + else { + mark_.setPositionY(mark.getCoords().getY() - group.distanceYProperty.get()); + } + mark = mark_; + } + } + + private void updateTop(int index) { + Mark mark = group.getComponents().get(index); + for(int i = index + 1; i < group.getComponents().size(); ++i) { + Mark mark_ = group.getComponents().get(i); + if(type == Constraint.Spacing) { + if(mark_ instanceof VisBody) { + double dy = mark_.getCoords().getY() - mark_.bottom(); + mark_.setPositionY(mark.top() + group.distanceYProperty.get() + dy); + } + else { + switch(mark_.getCoords().getYRef()) { + case Center: mark_.setPositionY(mark.top() + group.distanceYProperty.get() + mark_.height()/2); + break; + case Bottom: mark_.setPositionY(mark.top() + group.distanceYProperty.get()); + break; + case Top: mark_.setPositionY(mark.top() + group.distanceYProperty.get() + mark_.height()); + break; + } + } + } + else { + mark_.setPositionY(mark.getCoords().getY() + group.distanceYProperty.get()); + } + mark = mark_; + } + } + + @Override + protected void selectHandle(Circle shape) { + super.selectHandle(shape); + + if(shape == bottomcircle) { + distance_init = group.distanceYProperty.get(); + activeMark.setConstraintControl(getSelf(), ConstraintHandle.DOWN); + flagSelection(); + } + else { + distance_init = group.distanceYProperty.get(); + activeMark.setConstraintControl(getSelf(), ConstraintHandle.UP); + flagSelection(); + } + } + + protected void flagSelection() { + if(!ConstraintController.handlerhold && activeMark == null) { + yselectionIndex = 0; + yselectionPos = Double.MAX_VALUE; + } + else if(activeMark != null) { + yselectionIndex = group.getComponents().indexOf(activeMark); + yselectionPos = activeMark.getCoords().getY(); + } + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/GroupInteractor.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/GroupInteractor.java new file mode 100644 index 0000000000000000000000000000000000000000..facb43af80dbd1924d28cbe1193152f9fb9a92c0 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/GroupInteractor.java @@ -0,0 +1,122 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.Container; +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.types.AlignmentProperty; +import fr.inria.structgraphics.ui.utils.Cloner; +import javafx.scene.Group; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; + +public class GroupInteractor extends BodyInteractor { + + private Line xAxis, yAxis; + + public GroupInteractor(VisGroup group) { + super(group); + } + + + @Override + protected void drawHandler() { + super.drawHandler(); + handler.setStroke(group.isExternal() ? COL3 : COL3); + } + + + protected boolean isXNeeded() { + if(group.isExternal()) return true; + else { + Container container = group.getContainer(); + if(container instanceof VisGroup && ((VisGroup)container).getAlignYProperty().get() == AlignmentProperty.XSticky.Yes) + return false; + + return true; + } + } + + protected boolean isYNeeded() { + if(group.isExternal()) return true; + else { + Container container = group.getContainer(); + if(container instanceof VisGroup && ((VisGroup)container).getAlignXProperty().get() == AlignmentProperty.YSticky.Yes) + return false; + + return true; + } + } + + @Override + protected void drawBoundary() { + xAxis = new Line(0, 0, 100, 0); + xAxis.startXProperty().bind(left); + xAxis.endXProperty().bind(right); + getChildren().add(xAxis); + xAxis.setStroke(group.isExternal() ? COL3 : COL3); + if(!group.isExternal()) xAxis.getStrokeDashArray().addAll(2d); + else xAxis.getStrokeDashArray().clear(); + + yAxis = new Line(0, 0, 0, -100); + yAxis.startYProperty().bind(bottom); + yAxis.endYProperty().bind(top); + getChildren().add(yAxis); + yAxis.setStroke(group.isExternal() ? COL3 : COL3); + if(!group.isExternal()) yAxis.getStrokeDashArray().addAll(2d); + else yAxis.getStrokeDashArray().clear(); + + addToGhost(Cloner.clone(xAxis)); + addToGhost(Cloner.clone(yAxis)); + } + + public Line getXAxis() { + return xAxis; + } + + public Line getYAxis() { + return yAxis; + } + + @Override + public void refresh() { + super.refresh(); + addToGhost(Cloner.clone(xAxis)); + addToGhost(Cloner.clone(yAxis)); + + boolean isX = isXNeeded(); + boolean isY = isYNeeded(); + + xAxis.setVisible(isX); + yAxis.setVisible(isY); + handler.setVisible(isX || isY); + } + + @Override + public void setHighlight(boolean highlight) { + super.setHighlight(highlight && handler.isVisible()); + } + + @Override + public Group createCopy(double dx, double dy) { + Group group = new Group(); + Circle handler_ = new Circle(dx, dy, 10); + Line xAxis_ = new Line(xAxis.getStartX() + dx, xAxis.getStartY() + dy, xAxis.getEndX() + dx, xAxis.getEndY() + dy); + Line yAxis_ = new Line(yAxis.getStartX() + dx, yAxis.getStartY() + dy, yAxis.getEndX() + dx, yAxis.getEndY() + dy); + + group.getChildren().add(handler_); + group.getChildren().add(xAxis_); + group.getChildren().add(yAxis_); + + return group; + } + + @Override + public double getWidth() { + return Math.abs(xAxis.getEndX() - xAxis.getStartX()); + } + + @Override + public double getHeight() { + return Math.abs(yAxis.getEndY() - yAxis.getStartY()); + } +} + diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LabelCollection.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LabelCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..31d530f29be2facfa5bd90a3466668196f417abc --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LabelCollection.java @@ -0,0 +1,41 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.HashMap; +import java.util.Map; + +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import javafx.beans.property.Property; +import javafx.scene.Group; + +public class LabelCollection extends Group { + + private Mark mark; + private Map<DataVariable, NodeLabel> labels = new HashMap<DataVariable, NodeLabel>(); + + public LabelCollection(Mark mark) { + this.mark = mark; + } + + public void showVariable(DataVariable variable, Property property) { + NodeLabel node = labels.get(variable); + if(node != null && !variable.nodeShownProperty.get()) { + getChildren().remove(node); + labels.remove(variable); + } + else if(variable.nodeShownProperty.get()) { + NodeLabel label = new NodeLabel(mark, variable, property); + getChildren().add(label); + labels.put(variable, label); + } + } + + public void update() { + for(NodeLabel label: labels.values()) + label.update(); + } + + public boolean isLabelShown(DataVariable variable) { + return labels.get(variable) != null; + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/Legend.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/Legend.java new file mode 100644 index 0000000000000000000000000000000000000000..d072cf5ac7dfb6aee97df3cf0187bad0c93ecc47 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/Legend.java @@ -0,0 +1,158 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.SortedSet; +import java.util.TreeMap; + +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.spreadsheet.ComparableDataValue; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.spreadsheet.DiscreteTransformation; +import fr.inria.structgraphics.ui.spreadsheet.MappingProperty; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable.DataType; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Bounds; +import javafx.geometry.Pos; +import javafx.scene.Group; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; + +public class Legend extends Group { + protected Font textFont = new Font(12); + protected Font labelFont = Font.font("Verdana", FontWeight.BOLD, 12); + protected Color stroke = Color.GREY; + protected double rectStrokeWidth = 0.8; + protected Color textColor = Color.GREY; + + protected double vgap = 4; + protected double hgap = 2; + + protected double iconSize = 12; + + protected Text label; + + protected VBox values; + + protected VisBody group; + protected DataVariable variable; + + public Legend(VisBody group, DataVariable variable) { + this.group = group; + this.variable = variable; + + values = new VBox(vgap); + + label = new Text(); + label.setFont(labelFont); + label.setFill(textColor); + + getChildren().add(values); + + updateCaption(); + updateValues(); + } + + public Bounds bounds() { + return getLayoutBounds(); + } + + public void update() { + updateCaption(); + updateValues(); + } + + private void updateValues() { + values.getChildren().clear(); + + values.getChildren().add(label); + + if(variable.getType().equals(DataType.Functional) && variable.isNumeric()) { + // TODO: .... + + } else { + DiscreteTransformation transform = (DiscreteTransformation)variable.transformationProperty().get(); + TreeMap<MappingProperty, StringProperty> map = transform.getMap(); + + for(MappingProperty key: map.keySet()) { + + StringProperty prop = map.get(key); + Text textValue = new Text(prop.get()); + prop.addListener(new ChangeListener<String>() { + + @Override + public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { + textValue.setText(newValue); + } + }); + + textValue.setFont(textFont); + textValue.setFill(textColor); + + Group icon = new LegendIcon(iconSize, variable, key); + textValue.translateXProperty().set(icon.getTranslateX()); + + HBox box = new HBox(hgap); + box.alignmentProperty().set(Pos.CENTER_LEFT); + box.translateXProperty().set(hgap); + + box.getChildren().add(icon); + box.getChildren().add(textValue); + values.getChildren().add(box); + } + } + } + + + private void updateValues2() { + values.getChildren().clear(); + + values.getChildren().add(label); + + if(variable.getType().equals(DataType.Functional) && variable.isNumeric()) { + // TODO: .... + + } else { + SortedSet<ComparableDataValue> range = variable.getDiscreteValues(); + for(ComparableDataValue val: range) { + + Text textValue = new Text(variable.transformationProperty().get().toData(val.getValue())); + + val.getProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + textValue.setText(variable.transformationProperty().get().toData(val.getValue())); + } + }); + + + textValue.setFont(textFont); + textValue.setFill(textColor); + + Group icon = new LegendIcon(iconSize, variable, val); + + HBox box = new HBox(hgap); + box.alignmentProperty().set(Pos.CENTER_LEFT); + box.translateXProperty().set(hgap); + + box.getChildren().add(icon); + box.getChildren().add(textValue); + values.getChildren().add(box); + } + } + } + + private void updateCaption() { + label.textProperty().unbind(); + label.textProperty().bind(variable.getName(0)); + + label.xProperty().set(0); + label.yProperty().set(vgap + label.getLayoutBounds().getHeight()); + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LegendCollection.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LegendCollection.java new file mode 100644 index 0000000000000000000000000000000000000000..19036d27df9b229db69c6d74a711b96cca32f363 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LegendCollection.java @@ -0,0 +1,51 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import java.util.Hashtable; +import java.util.Map; + +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.scene.layout.VBox; + +public class LegendCollection extends VBox { + + private Map<DataVariable, Legend> map = new Hashtable<>(); + private VisBody vgroup; + + private double xGap = 20; + + private final static double VGap = 8; + + public LegendCollection(VisBody group) { + super(VGap); + this.vgroup = group; + + translateYProperty().bind(vgroup.getInteractor().top); + vgroup.getInteractor().right.addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + translateXProperty().set(xGap + vgroup.getInteractor().right.get()); + } + }); + } + + public void addLegend(DataVariable variable) { + if(map.containsKey(variable)) { + Legend legend = map.get(variable); + map.remove(variable); + getChildren().remove(legend); + } + else { + Legend legend = new Legend(vgroup, variable); + map.put(variable, legend); + getChildren().add(legend); + } + } + + public void update() { + for(Legend legend: map.values()) + legend.update(); + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LegendIcon.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LegendIcon.java new file mode 100644 index 0000000000000000000000000000000000000000..1fa5c83641809480b7b9fb0b3a87fc316df017bb --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/LegendIcon.java @@ -0,0 +1,110 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.types.ShapeProperty; +import fr.inria.structgraphics.ui.spreadsheet.ComparableDataValue; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import fr.inria.structgraphics.ui.spreadsheet.MappingProperty; +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; + +public class LegendIcon extends Group { + + private double size; + + public LegendIcon(double size, DataVariable variable, MappingProperty prop) { + this.size = size; + + if(variable.isShape()) createShape((ShapeProperty.Type)prop.getValue()); + else if(variable.isThickness()) createThickness((double)prop.getValue()); + else if(variable.isFill()) createFill((Color)prop.getValue()); + else if(variable.isStroke()) createStroke((Color)prop.getValue()); + else createSize((double)prop.getValue()); + } + + + public LegendIcon(double size, DataVariable variable, ComparableDataValue value) { + this.size = size; + + if(variable.isShape()) createShape((ShapeProperty.Type)value.getValue()); + else if(variable.isThickness()) createThickness((double)value.getValue()); + else if(variable.isFill()) createFill((Color)value.getValue()); + else if(variable.isStroke()) createStroke((Color)value.getValue()); + else createSize((double)value.getValue()); + } + + + private void createStroke(Color value) { + Line line = new Line(0, 0, size, size); + line.setStrokeWidth(1); + line.setStroke(value); + + getChildren().add(line); + } + + + private void createFill(Color value) { + Rectangle rect = new Rectangle(size, size); + rect.setStrokeWidth(.5); + rect.setStroke(Color.GRAY); + rect.setFill(value); + + getChildren().add(rect); + } + + private void createThickness(double value) { + Line line = new Line(0, 0, size, size); + line.setStrokeWidth(value); + line.setStroke(Color.BLACK); + + getChildren().add(line); + } + + private void createSize(double value) { + Circle circle = new Circle(0, 0, value/2); + this.translateXProperty().set(-value/2); + circle.setFill(null); + circle.setStroke(Color.GRAY); + + getChildren().add(circle); + } + + + private void createShape(ShapeProperty.Type shapeType) { + Shape shape = null; + + switch(shapeType) { + case Ellipse: + shape = new Circle(size/2, size/2, size/2); + break; + + case Triangle: + Path path = new Path(); + path.getElements().add(new MoveTo(size/2, 0)); + path.getElements().add(new LineTo(size, size)); + path.getElements().add(new LineTo(0, size)); + path.getElements().add(new LineTo(size/2, 0)); + shape = path; + break; + + default: + shape = new Rectangle(size, size); + shape.setStrokeWidth(.6); + shape.setStroke(Color.GRAY); + shape.setFill(null); + break; + } + + shape.setStrokeWidth(.6); + shape.setStroke(Color.GRAY); + shape.setFill(null); + + getChildren().add(shape); + } +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/NodeLabel.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/NodeLabel.java new file mode 100644 index 0000000000000000000000000000000000000000..5132eb90c898f9f0b0c7ab6a108e12ba913208e1 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/NodeLabel.java @@ -0,0 +1,133 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.LineConnectedCollection; +import fr.inria.structgraphics.graphics.Mark; +import fr.inria.structgraphics.graphics.ShapeMark; +import fr.inria.structgraphics.graphics.VisBody; +import fr.inria.structgraphics.graphics.VisCollection; +import fr.inria.structgraphics.graphics.VisFrame; +import fr.inria.structgraphics.ui.spreadsheet.DataVariable; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.Property; +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; + +public class NodeLabel extends Group implements InvalidationListener { + + protected Font labelFont = Font.font("Verdana", 10); + protected Font collectionFont = Font.font("Verdana", FontWeight.BOLD, 11); + + protected Color textColor = Color.GREY; + + private Text text; + private Mark mark; + protected DataVariable variable; + protected Property property; + + public NodeLabel(Mark mark, DataVariable variable, Property property) { + this.mark = mark; + this.variable = variable; + this.property = property; + + text = new Text(); + text.setFont((mark instanceof VisCollection) ? collectionFont : labelFont); + + if(variable.isStroke() || variable.isFill()) text.setFill((Color)property.getValue()); + else { + text.fillProperty().bind(((VisFrame)mark.getRoot()).stroke); + } + text.setTextAlignment(TextAlignment.CENTER); + + getChildren().add(text); + + showVariable(variable, property); + } + + public void showVariable(DataVariable variable, Property property) { + variable.transformationProperty().addListener(this); + update(); + } + + public void update() { + // TODO: this is a quick hack to show positive only values for the width variable + // TODO I need an explict interaction from behalf of the user + String value; + if(variable.isWidth()) { + value = variable.transformationProperty().get().toData(Math.abs((double)property.getValue())); + } else { + value = variable.isID() ? variable.transformationProperty().get().toData(mark.id.get()) : + variable.transformationProperty().get().toData(property.getValue()) ; + } + + text.setText(value); + + text.xProperty().set(-text.getLayoutBounds().getWidth()/2); + text.yProperty().set(text.getLayoutBounds().getHeight()/2); + + this.translateXProperty().set(0); + this.translateYProperty().set(0); + + if(variable.isStroke() && mark instanceof VisCollection) { + text.setTextAlignment(TextAlignment.LEFT); + + Mark child = ((VisCollection)mark).getComponents().get(((VisCollection)mark).getComponents().size() - 1); + double dx = child.left() - mark.coords.getX() + text.getLayoutBounds().getWidth()/2 + 25; + this.translateXProperty().set(dx); + + double dy = (-child.coords.getY() + mark.coords.getY()) - text.getLayoutBounds().getHeight()/4; + this.translateYProperty().set(dy); + } + else if(mark instanceof VisBody /*&& variable.isID()*/) { + double dx = (mark.left() + mark.right())/2 - mark.coords.getX(); + this.translateXProperty().set(dx); + + double dy = (-mark.top() + mark.coords.getY()) - text.getLayoutBounds().getHeight() - 8; + this.translateYProperty().set(dy); + } + else if(variable.isHeight()) { + VisBody container = ((ShapeMark)mark).getRootVirtualGroup(); + if(mark instanceof ShapeMark && (container instanceof LineConnectedCollection) + && ((LineConnectedCollection)container).hasConnections()) { + if(((ShapeMark)mark).hasOutwardConnections()) { + text.setTextAlignment(TextAlignment.RIGHT); + this.translateXProperty().set(-mark.width()/2 - text.getLayoutBounds().getWidth()/2 - 3); + } else { + text.setTextAlignment(TextAlignment.LEFT); + this.translateXProperty().set(mark.width()/2 + text.getLayoutBounds().getWidth()/2 + 3); + } + } + else { + if(mark.height.get() > 0) this.translateYProperty().set(-mark.height()/2 - text.getLayoutBounds().getHeight()); + else this.translateYProperty().set(-mark.height()/2 + text.getLayoutBounds().getHeight()/2); + } + } else if(variable.isX() || variable.isY()) { + this.translateYProperty().set(mark.height()/2 + text.getLayoutBounds().getHeight()/2); + } else if(variable.isWidth()) { + this.translateYProperty().set(-text.getLayoutBounds().getHeight()/4); + if(mark.width() > 0) this.translateXProperty().set(mark.width()/2 + text.getLayoutBounds().getWidth()/2 + 3); + else { + text.setTextAlignment(TextAlignment.RIGHT); + this.translateXProperty().set(mark.width()/2 - text.getLayoutBounds().getWidth()/2 - 3); + } + } else if(mark instanceof ShapeMark && variable.isConnectionNodeID()) { + if(((ShapeMark)mark).hasOutwardConnections()) { + text.setTextAlignment(TextAlignment.LEFT); + this.translateXProperty().set(mark.width()/2 + text.getLayoutBounds().getWidth()/2 + 3); + } else { + text.setTextAlignment(TextAlignment.RIGHT); + this.translateXProperty().set(-mark.width()/2 - text.getLayoutBounds().getWidth()/2 - 3); + } + } + } + + @Override + public void invalidated(Observable observable) { + update(); + } + +} diff --git a/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/SimpleGroupInteractor.java b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/SimpleGroupInteractor.java new file mode 100644 index 0000000000000000000000000000000000000000..ae8fa308ef6f80b9b7a278fd8d526c189fecfe78 --- /dev/null +++ b/src/fr/inria/structgraphics/ui/viscanvas/groupinteractors/SimpleGroupInteractor.java @@ -0,0 +1,111 @@ +package fr.inria.structgraphics.ui.viscanvas.groupinteractors; + +import fr.inria.structgraphics.graphics.VisGroup; +import fr.inria.structgraphics.ui.utils.Cloner; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; + +public class SimpleGroupInteractor extends BodyInteractor { + + private Rectangle boundary; + + public SimpleGroupInteractor(VisGroup group) { + super(group); + } + + private void updateBoundary() { + boundary.xProperty().set(left.get()); + boundary.yProperty().set(top.get()); + boundary.widthProperty().set(right.get() - left.get()); + boundary.heightProperty().set(bottom.get() - top.get()); + } + + @Override + protected void drawHandler() { + if(group.getContainer() instanceof VisGroup) { + handler = new Circle(0, 0, 0); + handler.setFill(null); + handler.setStroke(null); + } + else { + handler = new Circle(0, 0, 6); + handler.setFill(new Color(1, 1, 1, 0.1)); + handler.setStroke(Paint.valueOf("#303F9F")); + } + + getChildren().add(handler); + addToGhost(Cloner.clone(handler)); + } + + + @Override + protected void drawBoundary() { + boundary = new Rectangle(0,0,0,0); + updateBoundary(); + + left.addListener(new ChangeListener<Number>() { + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + updateBoundary(); + } + }); + right.addListener(new ChangeListener<Number>() { + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + updateBoundary(); + } + }); + top.addListener(new ChangeListener<Number>() { + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + updateBoundary(); + } + }); + bottom.addListener(new ChangeListener<Number>() { + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + updateBoundary(); + } + }); + + getChildren().add(boundary); + + boundary.strokeWidthProperty().set(0.3); + boundary.strokeProperty().set(Paint.valueOf("#303F9F")); + boundary.setFill(null); + boundary.getStrokeDashArray().addAll(2d); + + addToGhost(Cloner.clone(boundary)); + } + + @Override + public void refresh() { + super.refresh(); + addToGhost(Cloner.clone(boundary)); + } + + @Override + public Group createCopy(double dx, double dy) { + Group group = new Group(); + Circle handler_ = new Circle(dx, dy, 6); + + Rectangle boundary_ = new Rectangle(boundary.getX() + dx, boundary.getY() + dy, boundary.getWidth(), boundary.getHeight()); + + group.getChildren().add(handler_); + group.getChildren().add(boundary_); + + return group; + } + + @Override + public double getWidth() { // TODO + return Math.abs(boundary.widthProperty().get()); + } + + @Override + public double getHeight() { + return Math.abs(boundary.heightProperty().get()); + } + +} diff --git a/src/impl/build/transifex/JSON.java b/src/impl/build/transifex/JSON.java new file mode 100644 index 0000000000000000000000000000000000000000..044bf30f0ee42f24bfc267c33541e12dd6611539 --- /dev/null +++ b/src/impl/build/transifex/JSON.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.build.transifex; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class JSON { + private static final Pattern PAT_INTEGER = Pattern.compile("[-+]?[0-9]+|0[Xx][0-9]+"); //$NON-NLS-1$ + private static final Pattern PAT_DOUBLE = Pattern.compile("[+-]?[0-9]+([Ee][+-]?[0-9]+)?|[+-]?[0-9]*\\.[0-9]*([Ee][+-]?[0-9]+)?"); //$NON-NLS-1$ + private static final Pattern PAT_STRING = Pattern.compile("\"([^\\\\]+\\\\[\"'\\\\])*[^\"]*\"|'([^\\\\]+\\\\[\"'\\\\])*[^']*'"); //$NON-NLS-1$ + private static final Pattern PAT_BOOL = Pattern.compile("(true)|(false)"); //$NON-NLS-1$ + + private static Object parse(String s, int[] start, Matcher integerMatcher, Matcher doubleMatcher, Matcher stringMatcher, Matcher booleanMatcher) { + char[] c = s.toCharArray(); + skipSpace(s, start); + if (c[start[0]] == '[') { + start[0]++; + ArrayList<Object> a = new ArrayList<>(); + if (c[start[0]] == ']') { + start[0]++; + return a; + } + while (true) { + a.add(parse(s, start, integerMatcher, doubleMatcher, stringMatcher, booleanMatcher)); + boolean crlf = skipSpace(s, start); + char p = c[start[0]]; + if (p == ']') { + start[0]++; + return a; + } + if (p == ',') + start[0]++; + else if (!crlf) + throw new IllegalStateException(", or ] expected"); //$NON-NLS-1$ + } + } else if (c[start[0]] == '{') { + start[0]++; + HashMap<String, Object> a = new HashMap<>(); + while (true) { + String field = (String) parse(s, start, integerMatcher, doubleMatcher, stringMatcher, booleanMatcher); + boolean crlf = skipSpace(s, start); + if (c[start[0]] == ':') { + start[0]++; + a.put(field, parse(s, start, integerMatcher, doubleMatcher, stringMatcher, booleanMatcher)); + crlf = skipSpace(s, start); + } else + a.put(field, ""); //$NON-NLS-1$ + char p = c[start[0]]; + if (p == '}') { + start[0]++; + return a; + } + if (p == ',') + start[0]++; + else if (!crlf) { + start[0]++; +// throw new IllegalStateException(", or } expected at " + start[0]); //$NON-NLS-1$ + } + } + } + if (integerMatcher.find(start[0])) { + String substring = match(start, s, integerMatcher); + if (substring != null) return Integer.valueOf(substring); + } + if (doubleMatcher.find(start[0])) { + String substring = match(start, s, doubleMatcher); + if (substring != null) return Double.valueOf(substring); + } + if (stringMatcher.find(start[0])) { + String substring = match(start, s, stringMatcher); + if (substring != null) return substring.substring(1, substring.length() - 1); + } + if (booleanMatcher.find(start[0])) { + String substring = match(start, s, booleanMatcher); + if (substring != null) return Boolean.valueOf(substring); + } +// throw new IllegalStateException("unexpected end of data"); //$NON-NLS-1$ + return null; + } + + private static String match(int[] start, String s, Matcher matcher) { + int ms = matcher.start(); + int me = matcher.end(); + if (start[0] == ms) { + start[0] = me; + return s.substring(ms, me); + } + return null; + } + + public static boolean skipSpace(String s, int[] start) { + boolean ret = false; + while (true) { + char c = s.charAt(start[0]); + boolean crlf = (c == '\r') || (c == '\n'); + if ((c != ' ') && !crlf) + break; + if (crlf) + ret = true; + start[0]++; + } + return ret; + } + + @SuppressWarnings("unchecked") + public static <T> T parse(String json) { + Matcher integerMatcher = PAT_INTEGER.matcher(json); + Matcher doubleMatcher = PAT_DOUBLE.matcher(json); + Matcher stringMatcher = PAT_STRING.matcher(json); + Matcher booleanMatcher = PAT_BOOL.matcher(json); + return (T) parse(json, new int[]{0}, integerMatcher, doubleMatcher, stringMatcher, booleanMatcher); + } +} \ No newline at end of file diff --git a/src/impl/build/transifex/Transifex.java b/src/impl/build/transifex/Transifex.java new file mode 100644 index 0000000000000000000000000000000000000000..37e46964e8f69825c9b65c66b9228273a9496cf4 --- /dev/null +++ b/src/impl/build/transifex/Transifex.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.build.transifex; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class Transifex { + + private static final String CHARSET = "ISO-8859-1"; //$NON-NLS-1$ + private static final String FILE_NAME = "controlsfx_%1s.utf8"; //$NON-NLS-1$ + private static final String NEW_LINE = System.getProperty("line.separator"); //$NON-NLS-1$ + + private static final String BASE_URI = "https://www.transifex.com/api/2/"; //$NON-NLS-1$ + private static final String PROJECT_PATH = BASE_URI + "project/controlsfx/resource/controlsfx-core"; // list simple project details //$NON-NLS-1$ + private static final String PROJECT_DETAILS = BASE_URI + "project/controlsfx/resource/controlsfx-core?details"; // list all project details //$NON-NLS-1$ + private static final String LIST_TRANSLATIONS = BASE_URI + "project/controlsfx/languages/"; // list all translations //$NON-NLS-1$ + private static final String GET_TRANSLATION = BASE_URI + "project/controlsfx/resource/controlsfx-core/translation/%1s?file"; // gets a translation for one language //$NON-NLS-1$ + private static final String TRANSLATION_STATS = BASE_URI + "project/controlsfx/resource/controlsfx-core/stats/%1s/"; // gets a translation for one language //$NON-NLS-1$ + + private static final String USERNAME = System.getProperty("transifex.username"); //$NON-NLS-1$ + private static final String PASSWORD = System.getProperty("transifex.password"); //$NON-NLS-1$ + private static final boolean FILTER_INCOMPLETE_TRANSLATIONS = Boolean.parseBoolean(System.getProperty("transifex.filterIncompleteTranslations", "true")); + + public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException { + new Transifex().doTransifexCheck(); + } + + @SuppressWarnings("unchecked") + private void doTransifexCheck() { + System.out.println("=== Starting Transifex Check ==="); //$NON-NLS-1$ + + if (USERNAME == null || PASSWORD == null || USERNAME.isEmpty() || PASSWORD.isEmpty()) { + System.out.println(" transifex.username and transifex.password system properties must be specified"); //$NON-NLS-1$ + return; + } + + System.out.println(" Filtering out incomplete translations: " + FILTER_INCOMPLETE_TRANSLATIONS); + + Map<String,Object> projectDetails = JSON.parse(transifexRequest(PROJECT_DETAILS)); + List<Map<String, String>> availableLanguages = (List<Map<String, String>>) projectDetails.get("available_languages"); + + // main loop + availableLanguages.parallelStream() + .map(map -> map.get("code")) //$NON-NLS-1$ + .filter(this::filterOutIncompleteTranslations) + .forEach(this::downloadTranslation); + + System.out.println("Transifex Check Complete"); //$NON-NLS-1$ + } + + private String transifexRequest(String request, Object... args) { + Function<InputStream, String> consumer = inputStream -> { + StringBuilder response = new StringBuilder(); + try(BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream)) ) { + String line; + while((line = rd.readLine()) != null) { + response.append(line); + response.append(NEW_LINE); + } + } catch (IOException e) { + e.printStackTrace(); + } + + return response.toString(); + }; + return performTransifexTask(consumer, request, args); + } + + private static <T> T performTransifexTask(Function<InputStream, T> consumer, String request, Object... args) { + request = String.format(request, args); + + URL url; + HttpURLConnection connection = null; + try { + url = new URL(request); + connection = (HttpURLConnection)url.openConnection(); + connection.setRequestMethod("GET"); //$NON-NLS-1$ + connection.setUseCaches(false); + connection.setDoInput(true); + + // pass in username / password + String encoded = Base64.getEncoder().encodeToString((USERNAME+":"+PASSWORD).getBytes()); //$NON-NLS-1$ + connection.setRequestProperty("Authorization", "Basic "+encoded); //$NON-NLS-1$ //$NON-NLS-2$ + connection.setRequestProperty("Accept-Charset", CHARSET); //$NON-NLS-1$ + + return consumer.apply(connection.getInputStream()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + return null; + } + + private boolean filterOutIncompleteTranslations(String languageCode) { + // filter out any translation that does not have 100% completion and reviewed state. + // Returns a Map, for example: + // { + // untranslated_entities=8, + // last_commiter=eryzhikov, + // translated_entities=34, + // untranslated_words=16, + // translated_words=57, + // last_update=2014-09-12 08:44:33, + // reviewed_percentage=69%, + // reviewed=29, + // completed=80% + // } + Map<String, String> map = JSON.parse(transifexRequest(TRANSLATION_STATS, languageCode)); + String completed = map.getOrDefault("completed", "0%"); //$NON-NLS-1$ //$NON-NLS-2$ + String reviewed = map.getOrDefault("reviewed_percentage", "0%"); //$NON-NLS-1$ //$NON-NLS-2$ + boolean isAccepted = completed.equals("100%") && reviewed.equals("100%"); //$NON-NLS-1$ //$NON-NLS-2$ + + System.out.println(" Reviewing translation '" + languageCode + "'" + //$NON-NLS-1$ //$NON-NLS-2$ + "\tcompletion: " + completed + //$NON-NLS-1$ + ",\treviewed: " + reviewed + //$NON-NLS-1$ + "\t-> TRANSLATION" + (isAccepted ? " ACCEPTED" : " REJECTED")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + return isAccepted || !FILTER_INCOMPLETE_TRANSLATIONS; + } + + private void downloadTranslation(String languageCode) { + // Now we download the translations of the completed languages + System.out.println("\tDownloading translation file..."); //$NON-NLS-1$ + + Function<InputStream, Void> consumer = inputStream -> { + final String outputFile = "build/resources/main/" + String.format(FILE_NAME, languageCode); //$NON-NLS-1$ + + ReadableByteChannel rbc = Channels.newChannel(inputStream); + try (FileOutputStream fos = new FileOutputStream(outputFile)) { + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + }; + performTransifexTask(consumer, GET_TRANSLATION, languageCode); + } +} diff --git a/src/impl/org/controlsfx/ImplUtils.java b/src/impl/org/controlsfx/ImplUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..08bbbee58409eca5fc3d74c3ede75216074ff48b --- /dev/null +++ b/src/impl/org/controlsfx/ImplUtils.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.Pane; + +public class ImplUtils { + + private ImplUtils() { + // no-op + } + + public static void injectAsRootPane(Scene scene, Parent injectedParent, boolean useReflection) { + Parent originalParent = scene.getRoot(); + scene.setRoot(injectedParent); + + if (originalParent != null) { + getChildren(injectedParent, useReflection).add(0, originalParent); + + // copy in layout properties, etc, so that the dialogStack displays + // properly in (hopefully) whatever layout the owner node is in + injectedParent.getProperties().putAll(originalParent.getProperties()); + } + } + + // parent is where we want to inject the injectedParent. We then need to + // set the child of the injectedParent to include parent. + // The end result is that we've forced in the injectedParent node above parent. + public static void injectPane(Parent parent, Parent injectedParent, boolean useReflection) { + if (parent == null) { + throw new IllegalArgumentException("parent can not be null"); //$NON-NLS-1$ + } + + List<Node> ownerParentChildren = getChildren(parent.getParent(), useReflection); + + // we've got the children list, now we need to insert a temporary + // layout container holding our dialogs and opaque layer / effect + // in place of the owner (the owner will become a child of the dialog + // stack) + int ownerPos = ownerParentChildren.indexOf(parent); + ownerParentChildren.remove(ownerPos); + ownerParentChildren.add(ownerPos, injectedParent); + + // now we install the parent as a child of the injectedParent + getChildren(injectedParent, useReflection).add(0, parent); + + // copy in layout properties, etc, so that the dialogStack displays + // properly in (hopefully) whatever layout the owner node is in + injectedParent.getProperties().putAll(parent.getProperties()); + } + + public static void stripRootPane(Scene scene, Parent originalParent, boolean useReflection) { + Parent oldParent = scene.getRoot(); + getChildren(oldParent, useReflection).remove(originalParent); + originalParent.getStyleClass().remove("root"); //$NON-NLS-1$ + scene.setRoot(originalParent); + } + + public static List<Node> getChildren(Node n, boolean useReflection) { + return n instanceof Parent ? getChildren((Parent)n, useReflection) : Collections.emptyList(); + } + + public static List<Node> getChildren(Parent p, boolean useReflection) { + ObservableList<Node> children = null; + + // previously we used reflection immediately, now we try to avoid reflection + // by checking the type of the Parent. Still not great... + if (p instanceof Pane) { + // This should cover the majority of layout containers, including + // AnchorPane, FlowPane, GridPane, HBox, Pane, StackPane, TilePane, VBox + children = ((Pane)p).getChildren(); + } else if (p instanceof Group) { + children = ((Group)p).getChildren(); + } else if (p instanceof Control) { + Control c = (Control) p; + Skin<?> s = c.getSkin(); + children = s instanceof SkinBase ? ((SkinBase<?>)s).getChildren() : getChildrenReflectively(p); + } else if (useReflection) { + // we really want to avoid using this!!!! + children = getChildrenReflectively(p); + } + + if (children == null) { + throw new RuntimeException("Unable to get children for Parent of type " + p.getClass() + //$NON-NLS-1$ + ". useReflection is set to " + useReflection); //$NON-NLS-1$ + } + + return children == null ? FXCollections.emptyObservableList() : children; + } + + @SuppressWarnings("unchecked") + public static ObservableList<Node> getChildrenReflectively(Parent p) { + ObservableList<Node> children = null; + + try { + Method getChildrenMethod = Parent.class.getDeclaredMethod("getChildren"); //$NON-NLS-1$ + + if (getChildrenMethod != null) { + if (! getChildrenMethod.isAccessible()) { + getChildrenMethod.setAccessible(true); + } + children = (ObservableList<Node>) getChildrenMethod.invoke(p); + } else { + // uh oh, trouble + } + } catch (ReflectiveOperationException | IllegalArgumentException e) { + throw new RuntimeException("Unable to get children for Parent of type " + p.getClass(), e); //$NON-NLS-1$ + } + + return children; + } +} diff --git a/src/impl/org/controlsfx/autocompletion/AutoCompletionTextFieldBinding.java b/src/impl/org/controlsfx/autocompletion/AutoCompletionTextFieldBinding.java new file mode 100644 index 0000000000000000000000000000000000000000..67416a6ce12d9ec1d78c00be84b2d2db912eda32 --- /dev/null +++ b/src/impl/org/controlsfx/autocompletion/AutoCompletionTextFieldBinding.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.autocompletion; + +import java.util.Collection; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TextField; +import javafx.util.Callback; +import javafx.util.StringConverter; + +import org.controlsfx.control.textfield.AutoCompletionBinding; + +/** + * Represents a binding between a text field and a auto-completion popup + * + * @param <T> + */ +public class AutoCompletionTextFieldBinding<T> extends AutoCompletionBinding<T>{ + + /*************************************************************************** + * * + * Static properties and methods * + * * + **************************************************************************/ + + private static <T> StringConverter<T> defaultStringConverter() { + return new StringConverter<T>() { + @Override public String toString(T t) { + return t == null ? null : t.toString(); + } + @SuppressWarnings("unchecked") + @Override public T fromString(String string) { + return (T) string; + } + }; + } + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + /** + * String converter to be used to convert suggestions to strings. + */ + private StringConverter<T> converter; + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Creates a new auto-completion binding between the given textField + * and the given suggestion provider. + * + * @param textField + * @param suggestionProvider + */ + public AutoCompletionTextFieldBinding(final TextField textField, + Callback<ISuggestionRequest, Collection<T>> suggestionProvider) { + + this(textField, suggestionProvider, AutoCompletionTextFieldBinding + .<T>defaultStringConverter()); + } + + /** + * Creates a new auto-completion binding between the given textField + * and the given suggestion provider. + * + * @param textField + * @param suggestionProvider + */ + public AutoCompletionTextFieldBinding(final TextField textField, + Callback<ISuggestionRequest, Collection<T>> suggestionProvider, + final StringConverter<T> converter) { + + super(textField, suggestionProvider, converter); + this.converter = converter; + + getCompletionTarget().textProperty().addListener(textChangeListener); + getCompletionTarget().focusedProperty().addListener(focusChangedListener); + } + + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override public TextField getCompletionTarget(){ + return (TextField)super.getCompletionTarget(); + } + + /** {@inheritDoc} */ + @Override public void dispose(){ + getCompletionTarget().textProperty().removeListener(textChangeListener); + getCompletionTarget().focusedProperty().removeListener(focusChangedListener); + } + + /** {@inheritDoc} */ + @Override protected void completeUserInput(T completion){ + String newText = converter.toString(completion); + getCompletionTarget().setText(newText); + getCompletionTarget().positionCaret(newText.length()); + } + + + /*************************************************************************** + * * + * Event Listeners * + * * + **************************************************************************/ + + + private final ChangeListener<String> textChangeListener = new ChangeListener<String>() { + @Override public void changed(ObservableValue<? extends String> obs, String oldText, String newText) { + if (getCompletionTarget().isFocused()) { + setUserInput(newText); + } + } + }; + + private final ChangeListener<Boolean> focusChangedListener = new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> obs, Boolean oldFocused, Boolean newFocused) { + if(newFocused == false) + hidePopup(); + } + }; +} diff --git a/src/impl/org/controlsfx/autocompletion/SuggestionProvider.java b/src/impl/org/controlsfx/autocompletion/SuggestionProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..5c178033db807e2da84c2226d703a98baf29ce52 --- /dev/null +++ b/src/impl/org/controlsfx/autocompletion/SuggestionProvider.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2014, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.autocompletion; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javafx.util.Callback; + +import org.controlsfx.control.textfield.AutoCompletionBinding.ISuggestionRequest; + +/** + * This is a simple implementation of a generic suggestion provider callback. + * The complexity of suggestion generation is O(n) where n is the number of possible suggestions. + * + * @param <T> Type of suggestions + */ +public abstract class SuggestionProvider<T> implements Callback<ISuggestionRequest, Collection<T>>{ + + private final List<T> possibleSuggestions = new ArrayList<>(); + private final Object possibleSuggestionsLock = new Object(); + + + /** + * Add the given new possible suggestions to this SuggestionProvider + * @param newPossible + */ + public void addPossibleSuggestions(@SuppressWarnings("unchecked") T... newPossible){ + addPossibleSuggestions(Arrays.asList(newPossible)); + } + + /** + * Add the given new possible suggestions to this SuggestionProvider + * @param newPossible + */ + public void addPossibleSuggestions(Collection<T> newPossible){ + synchronized (possibleSuggestionsLock) { + possibleSuggestions.addAll(newPossible); + } + } + + /** + * Remove all current possible suggestions + */ + public void clearSuggestions(){ + synchronized (possibleSuggestionsLock) { + possibleSuggestions.clear(); + } + } + + @Override + public final Collection<T> call(final ISuggestionRequest request) { + List<T> suggestions = new ArrayList<>(); + if(!request.getUserText().isEmpty()){ + synchronized (possibleSuggestionsLock) { + for (T possibleSuggestion : possibleSuggestions) { + if(isMatch(possibleSuggestion, request)){ + suggestions.add(possibleSuggestion); + } + } + } + Collections.sort(suggestions, getComparator()); + } + return suggestions; + } + + /** + * Get the comparator to order the suggestions + * @return + */ + protected abstract Comparator<T> getComparator(); + + /** + * Check the given possible suggestion is a match (is a valid suggestion) + * @param suggestion + * @param request + * @return + */ + protected abstract boolean isMatch(T suggestion, ISuggestionRequest request); + + + /*************************************************************************** + * * + * Static methods * + * * + **************************************************************************/ + + + /** + * Create a default suggestion provider based on the toString() method of the generic objects + * @param possibleSuggestions All possible suggestions + * @return + */ + public static <T> SuggestionProvider<T> create(Collection<T> possibleSuggestions){ + return create(null, possibleSuggestions); + } + + /** + * Create a default suggestion provider based on the toString() method of the generic objects + * using the provided stringConverter + * + * @param stringConverter A stringConverter which converts generic T into a string + * @param possibleSuggestions All possible suggestions + * @return + */ + public static <T> SuggestionProvider<T> create(Callback<T, String> stringConverter, Collection<T> possibleSuggestions){ + SuggestionProviderString<T> suggestionProvider = new SuggestionProviderString<>(stringConverter); + suggestionProvider.addPossibleSuggestions(possibleSuggestions); + return suggestionProvider; + } + + + + /*************************************************************************** + * * + * Default implementations * + * * + **************************************************************************/ + + + /** + * This is a simple string based suggestion provider. + * All generic suggestions T are turned into strings for processing. + * + */ + private static class SuggestionProviderString<T> extends SuggestionProvider<T> { + + private Callback<T, String> stringConverter; + + private final Comparator<T> stringComparator = new Comparator<T>() { + @Override + public int compare(T o1, T o2) { + String o1str = stringConverter.call(o1); + String o2str = stringConverter.call(o2); + return o1str.compareTo(o2str); + } + }; + + /** + * Create a new SuggestionProviderString + * @param stringConverter + */ + public SuggestionProviderString(Callback<T, String> stringConverter){ + this.stringConverter = stringConverter; + + // In case no stringConverter was provided, use the default strategy + if(this.stringConverter == null){ + this.stringConverter = new Callback<T, String>() { + @Override + public String call(T obj) { + return obj != null ? obj.toString() : ""; //$NON-NLS-1$ + } + }; + } + } + + /**{@inheritDoc}*/ + @Override + protected Comparator<T> getComparator() { + return stringComparator; + } + + /**{@inheritDoc}*/ + @Override + protected boolean isMatch(T suggestion, ISuggestionRequest request) { + String userTextLower = request.getUserText().toLowerCase(); + String suggestionStr = suggestion.toString().toLowerCase(); + return suggestionStr.contains(userTextLower); + } + } +} diff --git a/src/impl/org/controlsfx/behavior/RangeSliderBehavior.java b/src/impl/org/controlsfx/behavior/RangeSliderBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..e0f16653bfaf1b0b53b7e1ccc806b12cec6c20a9 --- /dev/null +++ b/src/impl/org/controlsfx/behavior/RangeSliderBehavior.java @@ -0,0 +1,339 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.behavior; + +import static javafx.scene.input.KeyCode.DOWN; +import static javafx.scene.input.KeyCode.END; +import static javafx.scene.input.KeyCode.F4; +import static javafx.scene.input.KeyCode.HOME; +import static javafx.scene.input.KeyCode.KP_DOWN; +import static javafx.scene.input.KeyCode.KP_LEFT; +import static javafx.scene.input.KeyCode.KP_RIGHT; +import static javafx.scene.input.KeyCode.KP_UP; +import static javafx.scene.input.KeyCode.LEFT; +import static javafx.scene.input.KeyCode.RIGHT; +import static javafx.scene.input.KeyCode.UP; +import static javafx.scene.input.KeyEvent.KEY_RELEASED; + +import java.util.ArrayList; +import java.util.List; + +import javafx.event.EventType; +import javafx.geometry.Orientation; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.util.Callback; + +import org.controlsfx.control.RangeSlider; +import org.controlsfx.tools.Utils; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.behavior.OrientedKeyBinding; + +public class RangeSliderBehavior extends BehaviorBase<RangeSlider> { + + /************************************************************************** + * Setup KeyBindings * + * * + * We manually specify the focus traversal keys because Slider has * + * different usage for up/down arrow keys. * + *************************************************************************/ + private static final List<KeyBinding> RANGESLIDER_BINDINGS = new ArrayList<>(); + static { + RANGESLIDER_BINDINGS.add(new KeyBinding(F4, "TraverseDebug").alt().ctrl().shift()); //$NON-NLS-1$ + + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(LEFT, "DecrementValue")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_LEFT, "DecrementValue")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(UP, "IncrementValue").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_UP, "IncrementValue").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(RIGHT, "IncrementValue")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_RIGHT, "IncrementValue")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(DOWN, "DecrementValue").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_DOWN, "DecrementValue").vertical()); //$NON-NLS-1$ + + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(LEFT, "TraverseLeft").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_LEFT, "TraverseLeft").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(UP, "TraverseUp")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_UP, "TraverseUp")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(RIGHT, "TraverseRight").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_RIGHT, "TraverseRight").vertical()); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(DOWN, "TraverseDown")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new RangeSliderKeyBinding(KP_DOWN, "TraverseDown")); //$NON-NLS-1$ + + RANGESLIDER_BINDINGS.add(new KeyBinding(HOME, KEY_RELEASED, "Home")); //$NON-NLS-1$ + RANGESLIDER_BINDINGS.add(new KeyBinding(END, KEY_RELEASED, "End")); //$NON-NLS-1$ + } + + public RangeSliderBehavior(RangeSlider slider) { + super(slider, RANGESLIDER_BINDINGS); + } + + @Override protected void callAction(String s) { + if ("Home".equals(s) || "Home2".equals(s)) home(); //$NON-NLS-1$ //$NON-NLS-2$ + else if ("End".equals(s) || "End2".equals(s)) end(); //$NON-NLS-1$ //$NON-NLS-2$ + else if ("IncrementValue".equals(s) || "IncrementValue2".equals(s)) incrementValue(); //$NON-NLS-1$ //$NON-NLS-2$ + else if ("DecrementValue".equals(s) || "DecrementValue2".equals(s)) decrementValue(); //$NON-NLS-1$ //$NON-NLS-2$ + else super.callAction(s); + } + + /************************************************************************** + * State and Functions * + *************************************************************************/ + + private Callback<Void, FocusedChild> selectedValue; + public void setSelectedValue(Callback<Void, FocusedChild> c) { + selectedValue = c; + } + /** + * Invoked by the RangeSlider {@link Skin} implementation whenever a mouse press + * occurs on the "track" of the slider. This will cause the thumb to be + * moved by some amount. + * + * @param position The mouse position on track with 0.0 being beginning of + * track and 1.0 being the end + */ + public void trackPress(MouseEvent e, double position) { + // determine the percentage of the way between min and max + // represented by this mouse event + final RangeSlider rangeSlider = getControl(); + // If not already focused, request focus + if (!rangeSlider.isFocused()) { + rangeSlider.requestFocus(); + } + if (selectedValue != null) { + double newPosition; + if (rangeSlider.getOrientation().equals(Orientation.HORIZONTAL)) { + newPosition = position * (rangeSlider.getMax() - rangeSlider.getMin()) + rangeSlider.getMin(); + } else { + newPosition = (1 - position) * (rangeSlider.getMax() - rangeSlider.getMin()) + rangeSlider.getMin(); + } + + /** + * If the position is inferior to the current LowValue, this means + * the user clicked on the track to move the low thumb. If not, then + * it means the user wanted to move the high thumb. + */ + if (newPosition < rangeSlider.getLowValue()) { + rangeSlider.adjustLowValue(newPosition); + } else { + rangeSlider.adjustHighValue(newPosition); + } + } + } + + /** + */ + public void trackRelease(MouseEvent e, double position) { + } + + /** + * @param position The mouse position on track with 0.0 being beginning of + * track and 1.0 being the end + */ + public void lowThumbPressed(MouseEvent e, double position) { + // If not already focused, request focus + final RangeSlider rangeSlider = getControl(); + if (!rangeSlider.isFocused()) rangeSlider.requestFocus(); + rangeSlider.setLowValueChanging(true); + } + + /** + * @param position The mouse position on track with 0.0 being beginning of + * track and 1.0 being the end + */ + public void lowThumbDragged(MouseEvent e, double position) { + final RangeSlider rangeSlider = getControl(); + double newValue = Utils.clamp(rangeSlider.getMin(), + (position * (rangeSlider.getMax() - rangeSlider.getMin())) + rangeSlider.getMin(), + rangeSlider.getMax()); + rangeSlider.setLowValue(newValue); + } + + /** + * When lowThumb is released lowValueChanging should be set to false. + */ + public void lowThumbReleased(MouseEvent e) { + final RangeSlider rangeSlider = getControl(); + rangeSlider.setLowValueChanging(false); + // RT-15207 When snapToTicks is true, slider value calculated in drag + // is then snapped to the nearest tick on mouse release. + if (rangeSlider.isSnapToTicks()) { + rangeSlider.setLowValue(snapValueToTicks(rangeSlider.getLowValue())); + } + } + + void home() { + RangeSlider slider = (RangeSlider) getControl(); + slider.adjustHighValue(slider.getMin()); + } + + void decrementValue() { + RangeSlider slider = (RangeSlider) getControl(); + if (selectedValue != null) { + if (selectedValue.call(null) == FocusedChild.HIGH_THUMB) { + if (slider.isSnapToTicks()) + slider.adjustHighValue(slider.getHighValue() - computeIncrement()); + else + slider.decrementHighValue(); + } else { + if (slider.isSnapToTicks()) + slider.adjustLowValue(slider.getLowValue() - computeIncrement()); + else + slider.decrementLowValue(); + } + } + } + + void end() { + RangeSlider slider = (RangeSlider) getControl(); + slider.adjustHighValue(slider.getMax()); + } + + void incrementValue() { + RangeSlider slider = (RangeSlider) getControl(); + if (selectedValue != null) { + if (selectedValue.call(null) == FocusedChild.HIGH_THUMB) { + if (slider.isSnapToTicks()) + slider.adjustHighValue(slider.getHighValue() + computeIncrement()); + else + slider.incrementHighValue(); + } else { + if (slider.isSnapToTicks()) + slider.adjustLowValue(slider.getLowValue() + computeIncrement()); + else + slider.incrementLowValue(); + } + } + + } + + double computeIncrement() { + RangeSlider rangeSlider = (RangeSlider) getControl(); + double d = 0.0D; + if (rangeSlider.getMinorTickCount() != 0) + d = rangeSlider.getMajorTickUnit() / (double) (Math.max(rangeSlider.getMinorTickCount(), 0) + 1); + else + d = rangeSlider.getMajorTickUnit(); + if (rangeSlider.getBlockIncrement() > 0.0D && rangeSlider.getBlockIncrement() < d) + return d; + else + return rangeSlider.getBlockIncrement(); + } + + private double snapValueToTicks(double d) { + RangeSlider rangeSlider = (RangeSlider) getControl(); + double d1 = d; + double d2 = 0.0D; + if (rangeSlider.getMinorTickCount() != 0) + d2 = rangeSlider.getMajorTickUnit() / (double) (Math.max(rangeSlider.getMinorTickCount(), 0) + 1); + else + d2 = rangeSlider.getMajorTickUnit(); + int i = (int) ((d1 - rangeSlider.getMin()) / d2); + double d3 = (double) i * d2 + rangeSlider.getMin(); + double d4 = (double) (i + 1) * d2 + rangeSlider.getMin(); + d1 = Utils.nearest(d3, d1, d4); + return Utils.clamp(rangeSlider.getMin(), d1, rangeSlider.getMax()); + } + + // when high thumb is released, highValueChanging is set to false. + public void highThumbReleased(MouseEvent e) { + RangeSlider slider = (RangeSlider) getControl(); + slider.setHighValueChanging(false); + if (slider.isSnapToTicks()) + slider.setHighValue(snapValueToTicks(slider.getHighValue())); + } + + public void highThumbPressed(MouseEvent e, double position) { + RangeSlider slider = (RangeSlider) getControl(); + if (!slider.isFocused()) + slider.requestFocus(); + slider.setHighValueChanging(true); + } + + public void highThumbDragged(MouseEvent e, double position) { + RangeSlider slider = (RangeSlider) getControl(); + slider.setHighValue(Utils.clamp(slider.getMin(), position * (slider.getMax() - slider.getMin()) + slider.getMin(), slider.getMax())); + } + + public void moveRange(double position) { + RangeSlider slider = (RangeSlider) getControl(); + final double min = slider.getMin(); + final double max = slider.getMax(); + final double lowValue = slider.getLowValue(); + final double newLowValue = Utils.clamp(min, lowValue + position *(max-min) / + (slider.getOrientation() == Orientation.HORIZONTAL? slider.getWidth(): slider.getHeight()), max); + final double highValue = slider.getHighValue(); + final double newHighValue = Utils.clamp(min, highValue + position*(max-min) / + (slider.getOrientation() == Orientation.HORIZONTAL? slider.getWidth(): slider.getHeight()), max); + + if (newLowValue <= min || newHighValue >= max) return; + slider.setLowValueChanging(true); + slider.setHighValueChanging(true); + slider.setLowValue(newLowValue); + slider.setHighValue(newHighValue); + } + + public void confirmRange() { + RangeSlider slider = (RangeSlider) getControl(); + + slider.setLowValueChanging(false); + if (slider.isSnapToTicks()) { + slider.setLowValue(snapValueToTicks(slider.getLowValue())); + } + slider.setHighValueChanging(false); + if (slider.isSnapToTicks()) { + slider.setHighValue(snapValueToTicks(slider.getHighValue())); + } + + } + + public static class RangeSliderKeyBinding extends OrientedKeyBinding { + public RangeSliderKeyBinding(KeyCode code, String action) { + super(code, action); + } + + public RangeSliderKeyBinding(KeyCode code, EventType<KeyEvent> type, String action) { + super(code, type, action); + } + + public @Override boolean getVertical(Control control) { + return ((RangeSlider)control).getOrientation() == Orientation.VERTICAL; + } + } + + public enum FocusedChild { + LOW_THUMB, + HIGH_THUMB, + RANGE_BAR, + NONE + } +} + diff --git a/src/impl/org/controlsfx/behavior/RatingBehavior.java b/src/impl/org/controlsfx/behavior/RatingBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..284d299f8ca0e1d814ed02fe4dbb9bd41c1a9fa5 --- /dev/null +++ b/src/impl/org/controlsfx/behavior/RatingBehavior.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.behavior; + +import java.util.Collections; + +import org.controlsfx.control.Rating; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; + +public class RatingBehavior extends BehaviorBase<Rating> { + + public RatingBehavior(Rating control) { + super(control, Collections.<KeyBinding> emptyList()); + } +} \ No newline at end of file diff --git a/src/impl/org/controlsfx/behavior/SnapshotViewBehavior.java b/src/impl/org/controlsfx/behavior/SnapshotViewBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..33456ec991c3e1e2b568281de95dbe2ede894a63 --- /dev/null +++ b/src/impl/org/controlsfx/behavior/SnapshotViewBehavior.java @@ -0,0 +1,783 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.behavior; + +import impl.org.controlsfx.tools.rectangle.CoordinatePosition; +import impl.org.controlsfx.tools.rectangle.CoordinatePositions; +import impl.org.controlsfx.tools.rectangle.Rectangles2D; +import impl.org.controlsfx.tools.rectangle.change.MoveChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.NewChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.Rectangle2DChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToEastChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToNorthChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToNortheastChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToNorthwestChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToSouthChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToSoutheastChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToSouthwestChangeStrategy; +import impl.org.controlsfx.tools.rectangle.change.ToWestChangeStrategy; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.function.Consumer; + +import javafx.event.EventType; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; + +import org.controlsfx.control.SnapshotView; +import org.controlsfx.control.SnapshotView.Boundary; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; + +/** + * The behavior for the {@link SnapshotView}. It is concerned with creating and changing selections according to mouse + * events handed to {@link #handleMouseEvent(MouseEvent) handleMouseEvents}. + */ +public class SnapshotViewBehavior extends BehaviorBase<SnapshotView> { + + /** + * The percentage of the control's node's width/height used as a tolerance for determining whether the cursor is on + * an edge of the selection. + */ + private static final double RELATIVE_EDGE_TOLERANCE = 0.015; + + /* ************************************************************************ + * * + * Attributes * + * * + **************************************************************************/ + + /** + * The current selection change; might be {@code null}. + */ + private SelectionChange selectionChange; + + /** + * A function which sets the {@link SnapshotView#selectionChangingProperty() selectionChanging} property to the + * given value. + */ + private final Consumer<Boolean> setSelectionChanging; + + /* ************************************************************************ + * * + * Constructor * + * * + **************************************************************************/ + + /** + * Creates a new behavior for the specified {@link SnapshotView}. + * + * @param snapshotView + * the control which this behavior will control + */ + public SnapshotViewBehavior(SnapshotView snapshotView) { + super(snapshotView, new ArrayList<KeyBinding>()); + this.setSelectionChanging = createSetSelectionChanging(); + } + + /** + * Creates a function which sets the applied boolean to {@link SnapshotView#selectionChangingProperty()}. + * + * @return a Boolean {@link Consumer} + */ + private Consumer<Boolean> createSetSelectionChanging() { + return changing -> getControl().getProperties().put(SnapshotView.SELECTION_CHANGING_PROPERTY_KEY, changing); + } + + /* ************************************************************************ + * * + * Events * + * * + **************************************************************************/ + + /** + * Handles the specified mouse event (possibly by creating/changing/removing a selection) and returns the matching + * cursor. + * + * @param mouseEvent + * the handled {@link MouseEvent}; must not be {@code null} + * @return the cursor which will be used for this event + */ + public Cursor handleMouseEvent(MouseEvent mouseEvent) { + Objects.requireNonNull(mouseEvent, "The argument 'mouseEvent' must not be null."); //$NON-NLS-1$ + + EventType<? extends MouseEvent> eventType = mouseEvent.getEventType(); + SelectionEvent selectionEvent = createSelectionEvent(mouseEvent); + + if (eventType == MouseEvent.MOUSE_MOVED) { + return getCursor(selectionEvent); + } + if (eventType == MouseEvent.MOUSE_PRESSED) { + return handleMousePressedEvent(selectionEvent); + } + if (eventType == MouseEvent.MOUSE_DRAGGED) { + return handleMouseDraggedEvent(selectionEvent); + } + if (eventType == MouseEvent.MOUSE_RELEASED) { + return handleMouseReleasedEvent(selectionEvent); + } + + return Cursor.DEFAULT; + } + + // TRANSFORM MOUSE EVENT TO SELECTION EVENT + + /** + * Creates a selection event for the specified mouse event + * + * @param mouseEvent + * the {@link MouseEvent} for which the selection event will be created + * @return the {@link SelectionEvent} for the specified mouse event + */ + private SelectionEvent createSelectionEvent(MouseEvent mouseEvent) { + Point2D point = new Point2D(mouseEvent.getX(), mouseEvent.getY()); + Rectangle2D selectionBounds = createBoundsForCurrentBoundary(); + CoordinatePosition position = computePosition(point); + return new SelectionEvent(mouseEvent, point, selectionBounds, position); + } + + /** + * Returns the bounds according to the current {@link SnapshotView#selectionAreaBoundaryProperty() + * selectionAreaBoundary}. + * + * @return the bounds as a {@link Rectangle2D} + */ + private Rectangle2D createBoundsForCurrentBoundary() { + Boundary boundary = getControl().getSelectionAreaBoundary(); + switch (boundary) { + case CONTROL: + return new Rectangle2D(0, 0, getControlWidth(), getControlHeight()); + case NODE: + boolean nodeExists = getNode() != null; + if (nodeExists) { + Bounds nodeBounds = getNode().getBoundsInParent(); + return Rectangles2D.fromBounds(nodeBounds); + } else { + return Rectangle2D.EMPTY; + } + default: + throw new IllegalArgumentException("The boundary " + boundary + " is not fully implemented."); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Returns the position of the specified point relative to a possible selection. + * + * @param point + * the point (in the node's preferred coordinates) whose position will be computed + * + * @return the {@link CoordinatePosition} the event's point has relative to the control's current selection; if the + * selection is inactive this always returns {@link CoordinatePosition#OUT_OF_RECTANGLE}. + */ + private CoordinatePosition computePosition(Point2D point) { + boolean noSelection = !getControl().hasSelection() || !getControl().isSelectionActive(); + boolean controlHasNoSpace = getControlWidth() == 0 || getControlHeight() == 0; + if (noSelection || controlHasNoSpace) { + return CoordinatePosition.OUT_OF_RECTANGLE; + } + + double tolerance = computeTolerance(); + return computePosition(getSelection(), point, tolerance); + } + + /** + * Computes the tolerance which is used to determine whether the cursor is on an edge. + * + * @return the absolute tolerance + */ + private double computeTolerance() { + double controlMeanLength = Math.sqrt(getControlWidth() * getControlHeight()); + return RELATIVE_EDGE_TOLERANCE * controlMeanLength; + } + + /** + * Returns the position of the specified point relative to the specified selection with the specified tolerance. + * + * @param selection + * the selection relative to which the point's position will be computed; as a {@link Rectangle2D} + * @param point + * the {@link Point2D} whose position will be computed + * @param tolerance + * the absolute tolerance used to determine whether the point is on an edge + * + * @return the {@link CoordinatePosition} the event's point has relative to the control's current selection; if the + * selection is inactive this always returns {@link CoordinatePosition#OUT_OF_RECTANGLE}. + */ + private static CoordinatePosition computePosition(Rectangle2D selection, Point2D point, double tolerance) { + CoordinatePosition onEdge = CoordinatePositions.onEdges(selection, point, tolerance); + if (onEdge != null) { + return onEdge; + } else { + return CoordinatePositions.inRectangle(selection, point); + } + } + + // HANDLE SELECTION EVENTS + + /** + * Handles {@link MouseEvent#MOUSE_PRESSED} events by creating a new {@link #selectionChange} and beginning the + * change. + * + * @param selectionEvent + * the handled {@link SelectionEvent} + * @return the cursor which will be used while the selection changes + */ + private Cursor handleMousePressedEvent(SelectionEvent selectionEvent) { + if (selectionEvent.isPointInSelectionBounds()) { + // get all necessary information to create a selection change + Cursor cursor = getCursor(selectionEvent); + Rectangle2DChangeStrategy selectionChangeStrategy = getChangeStrategy(selectionEvent); + boolean deactivateSelectionIfClick = willDeactivateSelectionIfClick(selectionEvent); + + // create and begin the selection change + selectionChange = new SelectionChangeByStrategy( + getControl(), setSelectionChanging, selectionChangeStrategy, cursor, deactivateSelectionIfClick); + selectionChange.beginSelectionChange(selectionEvent.getPoint()); + } else { + // if the mouse is outside the legal bounds, the selection will not actually change + selectionChange = NoSelectionChange.INSTANCE; + } + + return selectionChange.getCursor(); + } + /** + * Handles {@link MouseEvent#MOUSE_DRAGGED} events by continuing the current {@link #selectionChange}. + * + * @param selectionEvent + * the handled {@link SelectionEvent} + * @return the cursor which will be used while the selection changes + */ + private Cursor handleMouseDraggedEvent(SelectionEvent selectionEvent) { + selectionChange.continueSelectionChange(selectionEvent.getPoint()); + return selectionChange.getCursor(); + } + + /** + * Handles {@link MouseEvent#MOUSE_RELEASED} events by ending the current {@link #selectionChange} and setting it to + * {@code null}. + * + * @param selectionEvent + * the handled {@link SelectionEvent} + * @return the cursor which will be used after the selection change ends + */ + private Cursor handleMouseReleasedEvent(SelectionEvent selectionEvent) { + // end and deactivate the selection change + selectionChange.endSelectionChange(selectionEvent.getPoint()); + selectionChange = null; + + return getCursor(selectionEvent); + } + + // CURSOR AND SELECTION CHANGE + + /** + * Returns the cursor which will be used for the specified selection event. + * + * @param selectionEvent + * the {@link SelectionEvent} to check + * @return the {@link Cursor} which will be used for the event + */ + private Cursor getCursor(SelectionEvent selectionEvent) { + // show the default cursor if the mouse is out of the selection bounds + if (!selectionEvent.isPointInSelectionBounds()) { + return getRegularCursor(); + } + + // otherwise pick a cursor from the relative position + switch (selectionEvent.getPosition()) { + case IN_RECTANGLE: + return Cursor.MOVE; + case OUT_OF_RECTANGLE: + return getRegularCursor(); + case NORTH_EDGE: + return Cursor.N_RESIZE; + case NORTHEAST_EDGE: + return Cursor.NE_RESIZE; + case EAST_EDGE: + return Cursor.E_RESIZE; + case SOUTHEAST_EDGE: + return Cursor.SE_RESIZE; + case SOUTH_EDGE: + return Cursor.S_RESIZE; + case SOUTHWEST_EDGE: + return Cursor.SW_RESIZE; + case WEST_EDGE: + return Cursor.W_RESIZE; + case NORTHWEST_EDGE: + return Cursor.NW_RESIZE; + default: + throw new IllegalArgumentException("The position " + selectionEvent.getPosition() //$NON-NLS-1$ + + " is not fully implemented."); //$NON-NLS-1$ + } + } + + /** + * @return the cursor from the {@link #getControl() control's} current {@link SnapshotView#cursorProperty() cursor} + */ + private Cursor getRegularCursor() { + return getControl().getCursor(); + } + + /** + * Returns the selection change strategy based on the specified selection event, which must be a + * {@link MouseEvent#MOUSE_PRESSED MOUSE_PRESSED} event. + * + * @param selectionEvent + * the {@link SelectionEvent} which will be checked + * @return the {@link Rectangle2DChangeStrategy} which will be executed based on the selection event + * @throws IllegalArgumentException + * if {@link SelectionEvent#getMouseEvent()} is not of type {@link MouseEvent#MOUSE_PRESSED}. + */ + private Rectangle2DChangeStrategy getChangeStrategy(SelectionEvent selectionEvent) { + boolean mousePressed = selectionEvent.getMouseEvent().getEventType() == MouseEvent.MOUSE_PRESSED; + if (!mousePressed) { + throw new IllegalArgumentException(); + } + + Rectangle2D selectionBounds = selectionEvent.getSelectionBounds(); + + switch (selectionEvent.getPosition()) { + case IN_RECTANGLE: + return new MoveChangeStrategy(getSelection(), selectionBounds); + case OUT_OF_RECTANGLE: + return new NewChangeStrategy( + isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case NORTH_EDGE: + return new ToNorthChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case NORTHEAST_EDGE: + return new ToNortheastChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case EAST_EDGE: + return new ToEastChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case SOUTHEAST_EDGE: + return new ToSoutheastChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case SOUTH_EDGE: + return new ToSouthChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case SOUTHWEST_EDGE: + return new ToSouthwestChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case WEST_EDGE: + return new ToWestChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + case NORTHWEST_EDGE: + return new ToNorthwestChangeStrategy( + getSelection(), isSelectionRatioFixed(), getSelectionRatio(), selectionBounds); + default: + throw new IllegalArgumentException("The position " + selectionEvent.getPosition() //$NON-NLS-1$ + + " is not fully implemented."); //$NON-NLS-1$ + } + } + + /** + * Checks whether the selection will be deactivated if the mouse is clicked at the {@link SelectionEvent}. + * + * @param selectionEvent + * the selection event which will be checked + * @return {@code true} if the selection event is such that the selection will be deactivated if the mouse is only + * clicked + */ + private static boolean willDeactivateSelectionIfClick(SelectionEvent selectionEvent) { + boolean rightClick = selectionEvent.getMouseEvent().getButton() == MouseButton.SECONDARY; + boolean outOfAreaClick = selectionEvent.getPosition() == CoordinatePosition.OUT_OF_RECTANGLE; + + return rightClick || outOfAreaClick; + } + + /* ************************************************************************ + * * + * Usability Access Functions to SnapshotView Properties * + * * + **************************************************************************/ + + /** + * The control's width. + * + * @return {@link SnapshotView#getWidth()} + */ + private double getControlWidth() { + return getControl().getWidth(); + } + + /** + * The control's height. + * + * @return {@link SnapshotView#getHeight()} + */ + private double getControlHeight() { + return getControl().getHeight(); + } + + /** + * The currently displayed node. + * + * @return {@link SnapshotView#getNode()} + */ + private Node getNode() { + return getControl().getNode(); + } + + /** + * The current selection. + * + * @return {@link SnapshotView#getSelection()} + */ + private Rectangle2D getSelection() { + return getControl().getSelection(); + } + /** + * Indicates whether the current selection has a fixed ratio. + * + * @return {@link SnapshotView#isSelectionRatioFixed()} + */ + private boolean isSelectionRatioFixed() { + return getControl().isSelectionRatioFixed(); + } + + /** + * The current selection's fixed ratio. + * + * @return {@link SnapshotView#getFixedSelectionRatio()} + */ + private double getSelectionRatio() { + return getControl().getFixedSelectionRatio(); + } + + /* ************************************************************************ + * * + * Inner Classes * + * * + **************************************************************************/ + + /** + * A selection event encapsulates a {@link MouseEvent} and adds some additional information like the coordinates + * relative to the node's preferred size and its position relative to a selection. + */ + private static class SelectionEvent { + + /** + * The {@link MouseEvent} for which this selection event was created. + */ + private final MouseEvent mouseEvent; + + /** + * The coordinates of the mouse event as a {@link Point2D}. + */ + private final Point2D point; + + /** + * The {@link Rectangle2D} within which any new selection must be contained. + */ + private final Rectangle2D selectionBounds; + + /** + * The {@link #point}'s position relative to a possible selection. + */ + private final CoordinatePosition position; + + /** + * Creates a new selection event with the specified arguments. + * + * @param mouseEvent + * the {@link MouseEvent} for which this selection event is created + * @param point + * the coordinates of the mouse event as a {@link Point2D} + * @param selectionBounds + * the {@link Rectangle2D} within which any new selection must be contained + * @param position + * the point's position relative to a possible selection + */ + public SelectionEvent( + MouseEvent mouseEvent, Point2D point, Rectangle2D selectionBounds, CoordinatePosition position) { + + this.mouseEvent = mouseEvent; + this.point = point; + this.selectionBounds = selectionBounds; + this.position = position; + } + + /** + * @return the mouse event for which this selection event was created + */ + public MouseEvent getMouseEvent() { + return mouseEvent; + } + + /** + * @return the coordinates of the mouse event in the nodes' preferred coordinates + */ + public Point2D getPoint() { + return point; + } + + /** + * @return the {@link Rectangle2D} within which any new selection must be contained + */ + public Rectangle2D getSelectionBounds() { + return selectionBounds; + } + + /** + * @return {@code true} if the {@link #getSelectionBounds() selectionBounds} contains the {@link #getPoint() + * point}; otherwise {@code false} + */ + public boolean isPointInSelectionBounds() { + return selectionBounds.contains(point); + } + + /** + * @return the {@link #getPoint() point}'s position relative to a possible selection. + */ + public CoordinatePosition getPosition() { + return position; + } + + } + + /** + * Handles the actual change of a selection when the mouse is pressed, dragged and released. + */ + private static interface SelectionChange { + + /** + * Begins the selection change at the specified point. + * + * @param point + * the starting point of the selection change + */ + public abstract void beginSelectionChange(Point2D point); + + /** + * Continues the selection change to the specified point. + * + * @param point + * the next point of this selection change + */ + public abstract void continueSelectionChange(Point2D point); + + /** + * Ends the selection change at the specified point. + * + * @param point + * the final point of this selection change + */ + public abstract void endSelectionChange(Point2D point); + + /** + * The cursor for this selection change. + * + * @return the cursor for this selection change + */ + public abstract Cursor getCursor(); + + } + + /** + * Implementation of {@link SelectionChange} which does not actually change anything. + */ + private static class NoSelectionChange implements SelectionChange { + + /** + * The singleton instance. + */ + public static final NoSelectionChange INSTANCE = new NoSelectionChange(); + + /** + * Private constructor for singleton. + */ + private NoSelectionChange() { + // nothing to do + } + + @Override + public void beginSelectionChange(Point2D point) { + // nothing to do + } + + @Override + public void continueSelectionChange(Point2D point) { + // nothing to do + } + + @Override + public void endSelectionChange(Point2D point) { + // nothing to do + } + + @Override + public Cursor getCursor() { + return Cursor.DEFAULT; + } + + } + + /** + * Executes the changes from a {@link Rectangle2DChangeStrategy} on a {@link SnapshotView}'s + * {@link SnapshotView#selectionProperty() selection} property. This includes to check whether the mouse moved from + * the change's start to end and to possibly deactivate the selection if not. + */ + private static class SelectionChangeByStrategy implements SelectionChange { + + // Attributes + + /** + * The snapshot view whose selection will be changed. + */ + private final SnapshotView snapshotView; + + /** + * A function which sets the {@link SnapshotView#selectionChangingProperty() selectionChanging} property to the + * given value. + */ + private final Consumer<Boolean> setSelectionChanging; + + /** + * The executed change strategy. + */ + private final Rectangle2DChangeStrategy selectionChangeStrategy; + + /** + * The cursor during the selection change. + */ + private final Cursor cursor; + + /** + * Indicates if the selection will be deactivated if the mouse is only clicked (e.g. does not move between start + * and end). + */ + private final boolean deactivateSelectionIfClick; + + /** + * The change's starting point. Used to check whether the mouse moved. + */ + private Point2D startingPoint; + + /** + * Set to true as soon as the mouse moved away from the starting point. + */ + private boolean mouseMoved; + + // Constructor + + /** + * Creates a new selection change for the specified {@link SnapshotView} using the specified + * {@link Rectangle2DChangeStrategy}. + * + * @param snapshotView + * the {@link SnapshotView} whose selection will be changed + * @param setSelectionChanging + * a function which sets the {@link SnapshotView#selectionChangingProperty() selectionChanging} + * property to the given value + * @param selectionChangeStrategy + * the {@link Rectangle2DChangeStrategy} used to change the selection + * @param cursor + * the {@link Cursor} used during the selection change + * @param deactivateSelectionIfClick + * indicates whether the selection will be deactivated if the change is only a click + */ + public SelectionChangeByStrategy( + SnapshotView snapshotView, Consumer<Boolean> setSelectionChanging, + Rectangle2DChangeStrategy selectionChangeStrategy, Cursor cursor, boolean deactivateSelectionIfClick) { + + this.snapshotView = snapshotView; + this.setSelectionChanging = setSelectionChanging; + this.selectionChangeStrategy = selectionChangeStrategy; + this.cursor = cursor; + this.deactivateSelectionIfClick = deactivateSelectionIfClick; + } + + // Selection Change + + @Override + public void beginSelectionChange(Point2D point) { + startingPoint = point; + setSelectionChanging.accept(true); + + Rectangle2D newSelection = selectionChangeStrategy.beginChange(point); + snapshotView.setSelection(newSelection); + } + + @Override + public void continueSelectionChange(Point2D point) { + updateMouseMoved(point); + + Rectangle2D newSelection = selectionChangeStrategy.continueChange(point); + snapshotView.setSelection(newSelection); + } + + @Override + public void endSelectionChange(Point2D point) { + updateMouseMoved(point); + + Rectangle2D newSelection = selectionChangeStrategy.endChange(point); + snapshotView.setSelection(newSelection); + + boolean deactivateSelection = deactivateSelectionIfClick && !mouseMoved; + if (deactivateSelection) { + snapshotView.setSelection(null); + } + setSelectionChanging.accept(false); + } + + /** + * Updates {@link #mouseMoved} by checking whether the specified point is different from the + * {@link #startingPoint}. + * + * @param point + * the point which will be compared to the {@link #startingPoint} + */ + private void updateMouseMoved(Point2D point) { + // if the mouse already moved, do nothing + if (mouseMoved) { + return; + } + + // if the mouse did not move yet, check whether it did now + boolean mouseMovedNow = !startingPoint.equals(point); + mouseMoved = mouseMovedNow; + } + + // Attribute Access + + @Override + public Cursor getCursor() { + return cursor; + } + + } + +} diff --git a/src/impl/org/controlsfx/control/validation/decoration-error.png b/src/impl/org/controlsfx/control/validation/decoration-error.png new file mode 100644 index 0000000000000000000000000000000000000000..237b39fa3d06785b37423903644f3c13c7967cb4 Binary files /dev/null and b/src/impl/org/controlsfx/control/validation/decoration-error.png differ diff --git a/src/impl/org/controlsfx/control/validation/decoration-warning.png b/src/impl/org/controlsfx/control/validation/decoration-warning.png new file mode 100644 index 0000000000000000000000000000000000000000..0d351c5bff6d7085586d58807b006ca05e75af88 Binary files /dev/null and b/src/impl/org/controlsfx/control/validation/decoration-warning.png differ diff --git a/src/impl/org/controlsfx/control/validation/required-indicator.png b/src/impl/org/controlsfx/control/validation/required-indicator.png new file mode 100644 index 0000000000000000000000000000000000000000..4410d654cf218f43f5e65f2dbf29c1b03a846044 Binary files /dev/null and b/src/impl/org/controlsfx/control/validation/required-indicator.png differ diff --git a/src/impl/org/controlsfx/i18n/Localization.java b/src/impl/org/controlsfx/i18n/Localization.java new file mode 100644 index 0000000000000000000000000000000000000000..59c134cf2afdc148d2cdf63c0c756275a5f48f6f --- /dev/null +++ b/src/impl/org/controlsfx/i18n/Localization.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.i18n; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public class Localization { + + private Localization() { + } + + public static final String KEY_PREFIX = "@@"; //$NON-NLS-1$ + + private static final String LOCALE_BUNDLE_NAME = "controlsfx"; //$NON-NLS-1$ + private static Locale locale = null; + + /** + * Returns the Locale object that is associated with ControlsFX. + * + * @return the global ControlsFX locale + */ + public static final Locale getLocale() { + // following allows us to have a "dynamic" locale based on OS/JDK + return locale == null ? Locale.getDefault() : locale; + } + + /** + * Sets locale which will be used as ControlsFX locale + * + * @param newLocale + * null is allowed and will be interpreted as default locale + */ + public static final void setLocale(final Locale newLocale) { + locale = newLocale; + } + + private static Locale resourceBundleLocale = null; // has to be null initially + private static ResourceBundle resourceBundle = null; + + private static synchronized final ResourceBundle getLocaleBundle() { + + Locale currentLocale = getLocale(); + if (!currentLocale.equals(resourceBundleLocale)) { + resourceBundleLocale = currentLocale; + resourceBundle = ResourceBundle.getBundle(LOCALE_BUNDLE_NAME, + resourceBundleLocale, Localization.class.getClassLoader()); + } + return resourceBundle; + + } + + /** + * Returns a string localized using currently set locale + * + * @param key resource bundle key + * @return localized text or formatted key if not found + */ + public static final String getString(final String key) { + try { + return getLocaleBundle().getString(key); + } catch (MissingResourceException ex) { + return String.format("<%s>", key); //$NON-NLS-1$ + } + } + + /** + * Converts text to localization key, + * currently by prepending it with the KEY_PREFIX + * + * @param text + * @return localization key + */ + public static final String asKey(String text) { + return KEY_PREFIX + text; + } + + /** + * Checks if the text is a localization key + * + * @param text + * @return true if text is a localization key + */ + public static final boolean isKey(String text) { + return text != null && text.startsWith(KEY_PREFIX); + } + + /** + * Tries to localize the text. If the text is a localization key - and attempt will be made to + * use it for localization, otherwise the text is returned as is + * + * @param text + * @return + */ + public static String localize(String text) { + return isKey(text) ? getString(text.substring(KEY_PREFIX.length()) + .trim()) : text; + } + +} diff --git a/src/impl/org/controlsfx/i18n/SimpleLocalizedStringProperty.java b/src/impl/org/controlsfx/i18n/SimpleLocalizedStringProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..44ce99643f391d20d1a7d4bc682faeb8c7159b87 --- /dev/null +++ b/src/impl/org/controlsfx/i18n/SimpleLocalizedStringProperty.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.i18n; + +import javafx.beans.property.SimpleStringProperty; + +/** + * A special implementation of string property which assumes that its content may be a key and + * attempts to get localized text resource base on it. + * + * It is intended for internal use only and will not work for bidirectional binding. + */ +public class SimpleLocalizedStringProperty extends SimpleStringProperty { + + public SimpleLocalizedStringProperty() { + } + + public SimpleLocalizedStringProperty(String initialValue) { + super(initialValue); + } + + public SimpleLocalizedStringProperty(Object bean, String name) { + super(bean, name); + } + + public SimpleLocalizedStringProperty(Object bean, String name, + String initialValue) { + super(bean, name, initialValue); + } + + @Override public String getValue() { + return Localization.localize(super.getValue()); + } +} diff --git a/src/impl/org/controlsfx/i18n/Translation.java b/src/impl/org/controlsfx/i18n/Translation.java new file mode 100644 index 0000000000000000000000000000000000000000..a972bb489287036b1d802edfe8c8b090cef21396 --- /dev/null +++ b/src/impl/org/controlsfx/i18n/Translation.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.i18n; + +import java.nio.file.Path; +import java.util.Locale; + +public class Translation implements Comparable<Translation> { + + private final String localeString; + private final Locale locale; + private final Path path; + + public Translation(String locale, Path path) { + this.localeString = locale; + this.path = path; + + String[] split = localeString.split("_"); //$NON-NLS-1$ + if (split.length == 1) { + this.locale = new Locale(localeString); + } else if (split.length == 2) { + this.locale = new Locale(split[0], split[1]); + } else if (split.length == 3) { + this.locale = new Locale(split[0], split[1], split[2]); + } else { + throw new IllegalArgumentException("Unknown locale string '" + locale + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + public final String getLocaleString() { + return localeString; + } + + public final Locale getLocale() { + return locale; + } + + public final Path getPath() { + return path; + } + + @Override public String toString() { + return localeString; + } + + @Override public int compareTo(Translation o) { + if (o == null) return 1; + return localeString.compareTo(o.localeString); + } +} diff --git a/src/impl/org/controlsfx/i18n/Translations.java b/src/impl/org/controlsfx/i18n/Translations.java new file mode 100644 index 0000000000000000000000000000000000000000..ab9035598dfa945ca6820b8002ff2f7e0ac5ec99 --- /dev/null +++ b/src/impl/org/controlsfx/i18n/Translations.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.i18n; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; + +public class Translations { + + private static List<Translation> translations = new ArrayList<>(); + + static { + // firstly try to read from the controlsfx jar + File file = new File(Translations.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + if (file.getName().endsWith(".jar")) { //$NON-NLS-1$ + Path jarFile = file.toPath(); + try (FileSystem fs = FileSystems.newFileSystem(jarFile, null)) { + fs.getRootDirectories().forEach(path -> loadFrom(path)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // look in src directory + if (translations.isEmpty()) { + // try to read the files from the local filesystem (good for when ControlsFX + // is being run from within a developers IDE) + Path srcDir = new File("src/main/resources").toPath(); //$NON-NLS-1$ + loadFrom(srcDir); + } + + // look in bin directory + if (translations.isEmpty()) { + Path binDir = new File("bin").toPath(); //$NON-NLS-1$ + loadFrom(binDir); + } + + // look in bin directory an alternative way (good for when running + // controlsfx-samples) + if (translations.isEmpty()) { + if (file.getAbsolutePath().endsWith("controlsfx" + File.separator + "bin")) { //$NON-NLS-1$ //$NON-NLS-2$ + loadFrom(file.toPath()); + } + } + + Collections.sort(translations); + } + + private static void loadFrom(Path rootPath) { + try (DirectoryStream<Path> stream = Files.newDirectoryStream(rootPath)) { + for (Path path : stream) { + String filename = path.getFileName().toString(); + + if (! filename.startsWith("controlsfx") && ! filename.endsWith(".properties")) { //$NON-NLS-1$ //$NON-NLS-2$ + continue; + } + + if ("controlsfx.properties".equals(filename)) { //$NON-NLS-1$ + translations.add(new Translation("en", path)); //$NON-NLS-1$ + } else if (filename.contains("_")) { //$NON-NLS-1$ + String locale = filename.substring(11, filename.indexOf(".properties")); //$NON-NLS-1$ + translations.add(new Translation(locale, path)); + } else { + throw new IllegalStateException("Unknown translation file '" + path + "'."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + } + } catch (IOException | DirectoryIteratorException x) { + // no-op + } + } + + private Translations() { + // no-op + } + + public static Optional<Translation> getTranslation(String localeString) { + for (Translation t : translations) { + if (localeString.equals(t.getLocaleString())) { + return Optional.of(t); + } + } + return Optional.empty(); + } + + public static List<Translation> getAllTranslations() { + return translations; + } + + public static List<Locale> getAllTranslationLocales() { + return translations.stream().map((Translation t) -> t.getLocale()).collect(Collectors.toList()); + } +} diff --git a/src/impl/org/controlsfx/skin/AutoCompletePopup.java b/src/impl/org/controlsfx/skin/AutoCompletePopup.java new file mode 100644 index 0000000000000000000000000000000000000000..e242d02b9bf18f7463c3d33158b48150526c841f --- /dev/null +++ b/src/impl/org/controlsfx/skin/AutoCompletePopup.java @@ -0,0 +1,233 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + + +import com.sun.javafx.event.EventHandlerManager; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.scene.Node; +import javafx.scene.control.PopupControl; +import javafx.scene.control.Skin; +import javafx.stage.Window; +import javafx.util.StringConverter; + +/** + * The auto-complete-popup provides an list of available suggestions in order + * to complete current user input. + */ +public class AutoCompletePopup<T> extends PopupControl{ + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + private final static int TITLE_HEIGHT = 28; // HACK: Hard-coded title-bar height + private final ObservableList<T> suggestions = FXCollections.observableArrayList(); + private StringConverter<T> converter; + /** + * The maximum number of rows to be visible in the popup when it is + * showing. By default this value is 10, but this can be changed to increase + * or decrease the height of the popup. + */ + private IntegerProperty visibleRowCount = new SimpleIntegerProperty(this, "visibleRowCount", 10); + + /*************************************************************************** + * * + * Inner classes * + * * + **************************************************************************/ + + /** + * Represents an Event which is fired when the user has selected a suggestion + * for auto-complete + * + * @param <TE> + */ + @SuppressWarnings("serial") + public static class SuggestionEvent<TE> extends Event { + @SuppressWarnings("rawtypes") + public static final EventType<SuggestionEvent> SUGGESTION = new EventType<>("SUGGESTION"); //$NON-NLS-1$ + + private final TE suggestion; + + public SuggestionEvent(TE suggestion) { + super(SUGGESTION); + this.suggestion = suggestion; + } + + /** + * Returns the suggestion which was chosen by the user + * @return + */ + public TE getSuggestion() { + return suggestion; + } + } + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Creates a new AutoCompletePopup + */ + public AutoCompletePopup(){ + this.setAutoFix(true); + this.setAutoHide(true); + this.setHideOnEscape(true); + + getStyleClass().add(DEFAULT_STYLE_CLASS); + } + + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + + /** + * Get the suggestions presented by this AutoCompletePopup + * @return + */ + public ObservableList<T> getSuggestions() { + return suggestions; + } + + /** + * Show this popup right below the given Node + * @param node + */ + public void show(Node node){ + + if(node.getScene() == null || node.getScene().getWindow() == null) + throw new IllegalStateException("Can not show popup. The node must be attached to a scene/window."); //$NON-NLS-1$ + + if(isShowing()){ + return; + } + + Window parent = node.getScene().getWindow(); + this.show( + parent, + parent.getX() + node.localToScene(0, 0).getX() + + node.getScene().getX(), + parent.getY() + node.localToScene(0, 0).getY() + + node.getScene().getY() + TITLE_HEIGHT); + + } + + /** + * Set the string converter used to turn a generic suggestion into a string + */ + public void setConverter(StringConverter<T> converter) { + this.converter = converter; + } + + /** + * Get the string converter used to turn a generic suggestion into a string + */ + public StringConverter<T> getConverter() { + return converter; + } + + public final void setVisibleRowCount(int value) { + visibleRowCount.set(value); + } + + public final int getVisibleRowCount() { + return visibleRowCount.get(); + } + + public final IntegerProperty visibleRowCountProperty() { + return visibleRowCount; + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + + private final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); + + public final ObjectProperty<EventHandler<SuggestionEvent<T>>> onSuggestionProperty() { return onSuggestion; } + public final void setOnSuggestion(EventHandler<SuggestionEvent<T>> value) { onSuggestionProperty().set(value); } + public final EventHandler<SuggestionEvent<T>> getOnSuggestion() { return onSuggestionProperty().get(); } + private ObjectProperty<EventHandler<SuggestionEvent<T>>> onSuggestion = new ObjectPropertyBase<EventHandler<SuggestionEvent<T>>>() { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override protected void invalidated() { + eventHandlerManager.setEventHandler(SuggestionEvent.SUGGESTION, (EventHandler<SuggestionEvent>)(Object)get()); + } + + @Override + public Object getBean() { + return AutoCompletePopup.this; + } + + @Override + public String getName() { + return "onSuggestion"; //$NON-NLS-1$ + } + }; + + /**{@inheritDoc}*/ + @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + return super.buildEventDispatchChain(tail).append(eventHandlerManager); + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + public static final String DEFAULT_STYLE_CLASS = "auto-complete-popup"; //$NON-NLS-1$ + + @Override + protected Skin<?> createDefaultSkin() { + return new AutoCompletePopupSkin<>(this); + } + +} diff --git a/src/impl/org/controlsfx/skin/AutoCompletePopupSkin.java b/src/impl/org/controlsfx/skin/AutoCompletePopupSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..0edfac6d90dcaac8c3952d1bc54c89bf2e24fe19 --- /dev/null +++ b/src/impl/org/controlsfx/skin/AutoCompletePopupSkin.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2014, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.beans.binding.Bindings; +import javafx.event.Event; +import javafx.scene.Node; +import javafx.scene.control.ListView; +import javafx.scene.control.Skin; +import javafx.scene.control.cell.TextFieldListCell; +import javafx.scene.input.MouseButton; +import org.controlsfx.control.textfield.AutoCompletionBinding; + + +public class AutoCompletePopupSkin<T> implements Skin<AutoCompletePopup<T>> { + + private final AutoCompletePopup<T> control; + private final ListView<T> suggestionList; + final int LIST_CELL_HEIGHT = 24; + + public AutoCompletePopupSkin(AutoCompletePopup<T> control){ + this.control = control; + suggestionList = new ListView<>(control.getSuggestions()); + + suggestionList.getStyleClass().add(AutoCompletePopup.DEFAULT_STYLE_CLASS); + + suggestionList.getStylesheets().add(AutoCompletionBinding.class + .getResource("autocompletion.css").toExternalForm()); //$NON-NLS-1$ + /** + * Here we bind the prefHeightProperty to the minimum height between the + * max visible rows and the current items list. We also add an arbitrary + * 5 number because when we have only one item we have the vertical + * scrollBar showing for no reason. + */ + suggestionList.prefHeightProperty().bind( + Bindings.min(control.visibleRowCountProperty(), Bindings.size(suggestionList.getItems())) + .multiply(LIST_CELL_HEIGHT).add(18)); + suggestionList.setCellFactory(TextFieldListCell.forListView(control.getConverter())); + + //Allowing the user to control ListView width. + suggestionList.prefWidthProperty().bind(control.prefWidthProperty()); + suggestionList.maxWidthProperty().bind(control.maxWidthProperty()); + suggestionList.minWidthProperty().bind(control.minWidthProperty()); + registerEventListener(); + } + + private void registerEventListener(){ + suggestionList.setOnMouseClicked(me -> { + if (me.getButton() == MouseButton.PRIMARY){ + onSuggestionChoosen(suggestionList.getSelectionModel().getSelectedItem()); + } + }); + + + suggestionList.setOnKeyPressed(ke -> { + switch (ke.getCode()) { + case ENTER: + onSuggestionChoosen(suggestionList.getSelectionModel().getSelectedItem()); + break; + case ESCAPE: + if (control.isHideOnEscape()) { + control.hide(); + } + break; + default: + break; + } + }); + } + + private void onSuggestionChoosen(T suggestion){ + if(suggestion != null) { + Event.fireEvent(control, new AutoCompletePopup.SuggestionEvent<>(suggestion)); + } + } + + + @Override + public Node getNode() { + return suggestionList; + } + + @Override + public AutoCompletePopup<T> getSkinnable() { + return control; + } + + @Override + public void dispose() { + } +} diff --git a/src/impl/org/controlsfx/skin/BreadCrumbBarSkin.java b/src/impl/org/controlsfx/skin/BreadCrumbBarSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..1bd8f98e4b82e936df1809a01373d8690dd5e861 --- /dev/null +++ b/src/impl/org/controlsfx/skin/BreadCrumbBarSkin.java @@ -0,0 +1,381 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeItem.TreeModificationEvent; +import javafx.scene.paint.Color; +import javafx.scene.shape.ArcTo; +import javafx.scene.shape.ClosePath; +import javafx.scene.shape.HLineTo; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.util.Callback; + +import org.controlsfx.control.BreadCrumbBar; +import org.controlsfx.control.BreadCrumbBar.BreadCrumbActionEvent; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; +import com.sun.javafx.scene.traversal.Algorithm; +import com.sun.javafx.scene.traversal.Direction; +import com.sun.javafx.scene.traversal.ParentTraversalEngine; +import com.sun.javafx.scene.traversal.TraversalContext; + +/** + * Basic Skin implementation for the {@link BreadCrumbBar} + * + * @param <T> + */ +public class BreadCrumbBarSkin<T> extends BehaviorSkinBase<BreadCrumbBar<T>, BehaviorBase<BreadCrumbBar<T>>> { + + private static final String STYLE_CLASS_FIRST = "first"; //$NON-NLS-1$ + + public BreadCrumbBarSkin(final BreadCrumbBar<T> control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + control.selectedCrumbProperty().addListener(selectedPathChangeListener); + updateSelectedPath(getSkinnable().selectedCrumbProperty().get(), null); + fixFocusTraversal(); + } + + // https://bitbucket.org/controlsfx/controlsfx/issue/453/breadcrumbbar-keyboard-focus-traversal-is + // ContainerTabOrder will fail with LEFT/RIGHT navigation, since the buttons in bread crumb overlap + private void fixFocusTraversal() { + + ParentTraversalEngine engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() { + + @Override + public Node select(Node owner, Direction dir, TraversalContext context) { + Node node = null; + int idx = getChildren().indexOf(owner); + switch(dir) { + case NEXT: + case NEXT_IN_LINE: + case RIGHT: + if (idx < getChildren().size() - 1) { + node = getChildren().get(idx+1); + } + break; + case PREVIOUS: + case LEFT: + if (idx > 0) { + node = getChildren().get(idx-1); + } + break; + } + return node; + } + + @Override + public Node selectFirst(TraversalContext context) { + Node first = null; + if (!getChildren().isEmpty()) { + first = getChildren().get(0); + } + return first; + } + + @Override + public Node selectLast(TraversalContext context) { + Node last = null; + if (!getChildren().isEmpty()) { + last = getChildren().get(getChildren().size()-1); + } + return last; + } + }); + engine.setOverriddenFocusTraversability(false); + getSkinnable().setImpl_traversalEngine(engine); + + } + + private final ChangeListener<TreeItem<T>> selectedPathChangeListener = + (obs, oldItem, newItem) -> updateSelectedPath(newItem, oldItem); + + private void updateSelectedPath(TreeItem<T> newTarget, TreeItem<T> oldTarget) { + if(oldTarget != null){ + // remove old listener + oldTarget.removeEventHandler( + TreeItem.childrenModificationEvent(), treeChildrenModifiedHandler); + } + if(newTarget != null){ + // add new listener + newTarget.addEventHandler(TreeItem.childrenModificationEvent(), treeChildrenModifiedHandler); + } + updateBreadCrumbs(); + } + + + private final EventHandler<TreeModificationEvent<Object>> treeChildrenModifiedHandler = + args -> updateBreadCrumbs(); + + + private void updateBreadCrumbs() { + final BreadCrumbBar<T> buttonBar = getSkinnable(); + final TreeItem<T> pathTarget = buttonBar.getSelectedCrumb(); + final Callback<TreeItem<T>, Button> factory = buttonBar.getCrumbFactory(); + + getChildren().clear(); + + if(pathTarget != null){ + List<TreeItem<T>> crumbs = constructFlatPath(pathTarget); + + for (int i=0; i < crumbs.size(); i++) { + Button crumb = createCrumb(factory, crumbs.get(i)); + crumb.setMnemonicParsing(false); + if (i == 0) { + if (! crumb.getStyleClass().contains(STYLE_CLASS_FIRST)) { + crumb.getStyleClass().add(STYLE_CLASS_FIRST); + } + } else { + crumb.getStyleClass().remove(STYLE_CLASS_FIRST); + } + + getChildren().add(crumb); + } + } + } + + @Override protected void layoutChildren(double x, double y, double w, double h) { + for (int i = 0; i < getChildren().size(); i++) { + Node n = getChildren().get(i); + + double nw = snapSize(n.prefWidth(h)); + double nh = snapSize(n.prefHeight(-1)); + + if (i > 0) { + // We have to position the bread crumbs slightly overlapping + double ins = n instanceof BreadCrumbButton ? ((BreadCrumbButton)n).getArrowWidth() : 0; + x = snapPosition(x - ins); + } + + n.resize(nw, nh); + n.relocate(x, y); + x += nw; + } + } + + /** + * Construct a flat list for the crumbs + * @param bottomMost The crumb node at the end of the path + * @return + */ + private List<TreeItem<T>> constructFlatPath(TreeItem<T> bottomMost){ + List<TreeItem<T>> path = new ArrayList<>(); + + TreeItem<T> current = bottomMost; + do { + path.add(current); + current = current.getParent(); + } while (current != null); + + Collections.reverse(path); + return path; + } + + private Button createCrumb( + final Callback<TreeItem<T>, Button> factory, + final TreeItem<T> selectedCrumb) { + + Button crumb = factory.call(selectedCrumb); + + crumb.getStyleClass().add("crumb"); //$NON-NLS-1$ + + // We want all buttons to have the same height + // so we bind their preferred height to the enclosing container +// crumb.prefHeightProperty().bind(getSkinnable().heightProperty()); + + // listen to the action event of each bread crumb + crumb.setOnAction(ae -> onBreadCrumbAction(selectedCrumb)); + + return crumb; + } + + /** + * Occurs when a bread crumb gets the action event + * + * @param crumbModel The crumb which received the action event + */ + protected void onBreadCrumbAction(final TreeItem<T> crumbModel){ + final BreadCrumbBar<T> breadCrumbBar = getSkinnable(); + + // fire the composite event in the breadCrumbBar + Event.fireEvent(breadCrumbBar, new BreadCrumbActionEvent<>(crumbModel)); + + // navigate to the clicked crumb + if(breadCrumbBar.isAutoNavigationEnabled()){ + breadCrumbBar.setSelectedCrumb(crumbModel); + } + } + + + + + /** + * Represents a BreadCrumb Button + * + * <pre> + * ---------- + * \ \ + * / / + * ---------- + * </pre> + * + * + */ + public static class BreadCrumbButton extends Button { + + private final ObjectProperty<Boolean> first = new SimpleObjectProperty<>(this, "first"); //$NON-NLS-1$ + + private final double arrowWidth = 5; + private final double arrowHeight = 20; + + /** + * Create a BreadCrumbButton + * + * @param text Buttons text + */ + public BreadCrumbButton(String text){ + this(text, null); + } + + /** + * Create a BreadCrumbButton + * @param text Buttons text + * @param gfx Gfx of the Button + */ + public BreadCrumbButton(String text, Node gfx){ + super(text, gfx); + first.set(false); + + getStyleClass().addListener(new InvalidationListener() { + @Override public void invalidated(Observable arg0) { + updateShape(); + } + }); + + updateShape(); + } + + private void updateShape(){ + this.setShape(createButtonShape()); + } + + + /** + * Gets the crumb arrow with + * @return + */ + public double getArrowWidth(){ + return arrowWidth; + } + + /** + * Create an arrow path + * + * Based upon Uwe / Andy Till code snippet found here: + * @see http://ustesis.wordpress.com/2013/11/04/implementing-breadcrumbs-in-javafx/ + * @return + */ + private Path createButtonShape(){ + // build the following shape (or home without left arrow) + + // -------- + // \ \ + // / / + // -------- + Path path = new Path(); + + // begin in the upper left corner + MoveTo e1 = new MoveTo(0, 0); + path.getElements().add(e1); + + // draw a horizontal line that defines the width of the shape + HLineTo e2 = new HLineTo(); + // bind the width of the shape to the width of the button + e2.xProperty().bind(this.widthProperty().subtract(arrowWidth)); + path.getElements().add(e2); + + // draw upper part of right arrow + LineTo e3 = new LineTo(); + // the x endpoint of this line depends on the x property of line e2 + e3.xProperty().bind(e2.xProperty().add(arrowWidth)); + e3.setY(arrowHeight / 2.0); + path.getElements().add(e3); + + // draw lower part of right arrow + LineTo e4 = new LineTo(); + // the x endpoint of this line depends on the x property of line e2 + e4.xProperty().bind(e2.xProperty()); + e4.setY(arrowHeight); + path.getElements().add(e4); + + // draw lower horizontal line + HLineTo e5 = new HLineTo(0); + path.getElements().add(e5); + + if(! getStyleClass().contains(STYLE_CLASS_FIRST)){ + // draw lower part of left arrow + // we simply can omit it for the first Button + LineTo e6 = new LineTo(arrowWidth, arrowHeight / 2.0); + path.getElements().add(e6); + }else{ + // draw an arc for the first bread crumb + ArcTo arcTo = new ArcTo(); + arcTo.setSweepFlag(true); + arcTo.setX(0); + arcTo.setY(0); + arcTo.setRadiusX(15.0f); + arcTo.setRadiusY(15.0f); + path.getElements().add(arcTo); + } + + // close path + ClosePath e7 = new ClosePath(); + path.getElements().add(e7); + // this is a dummy color to fill the shape, it won't be visible + path.setFill(Color.BLACK); + + return path; + } + } +} diff --git a/src/impl/org/controlsfx/skin/CheckComboBoxSkin.java b/src/impl/org/controlsfx/skin/CheckComboBoxSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..1e7d1f7c720f7332c7144a0d3d2333035949440a --- /dev/null +++ b/src/impl/org/controlsfx/skin/CheckComboBoxSkin.java @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.cell.CheckBoxListCell; +import javafx.util.Callback; + +import org.controlsfx.control.CheckComboBox; + +import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; +import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin; + +public class CheckComboBoxSkin<T> extends BehaviorSkinBase<CheckComboBox<T>, BehaviorBase<CheckComboBox<T>>> { + + /************************************************************************** + * + * Static fields + * + **************************************************************************/ + + + + /************************************************************************** + * + * fields + * + **************************************************************************/ + + // visuals + private final ComboBox<T> comboBox; + private final ListCell<T> buttonCell; + + // data + private final CheckComboBox<T> control; + private final ObservableList<T> items; + private final ReadOnlyUnbackedObservableList<Integer> selectedIndices; + private final ReadOnlyUnbackedObservableList<T> selectedItems; + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + @SuppressWarnings("unchecked") + public CheckComboBoxSkin(final CheckComboBox<T> control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + this.control = control; + this.items = control.getItems(); + + selectedIndices = (ReadOnlyUnbackedObservableList<Integer>) control.getCheckModel().getCheckedIndices(); + selectedItems = (ReadOnlyUnbackedObservableList<T>) control.getCheckModel().getCheckedItems(); + + comboBox = new ComboBox<T>(items) { + @Override protected javafx.scene.control.Skin<?> createDefaultSkin() { + return new ComboBoxListViewSkin<T>(this) { + // overridden to prevent the popup from disappearing + @Override protected boolean isHideOnClickEnabled() { + return false; + } + }; + } + }; + comboBox.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + + // installs a custom CheckBoxListCell cell factory + comboBox.setCellFactory(new Callback<ListView<T>, ListCell<T>>() { + @Override public ListCell<T> call(ListView<T> listView) { + CheckBoxListCell<T> result = new CheckBoxListCell<>(item -> control.getItemBooleanProperty(item)); + result.converterProperty().bind(control.converterProperty()); + return result; + }; + }); + + // we render the selection into a custom button cell, so that it can + // be pretty printed (e.g. 'Item 1, Item 2, Item 10'). + buttonCell = new ListCell<T>() { + @Override protected void updateItem(T item, boolean empty) { + // we ignore whatever item is selected, instead choosing + // to display the selected item text using commas to separate + // each item + setText(buildString()); + } + }; + comboBox.setButtonCell(buttonCell); + comboBox.setValue((T)buildString()); + + // The zero is a dummy value - it just has to be legally within the bounds of the + // item count for the CheckComboBox items list. + selectedIndices.addListener((ListChangeListener<Integer>) c -> buttonCell.updateIndex(0)); + + getChildren().add(comboBox); + } + + + /************************************************************************** + * + * Overriding public API + * + **************************************************************************/ + + @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return comboBox.minWidth(height); + } + + @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return comboBox.minHeight(width); + } + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return comboBox.prefWidth(height); + } + + @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return comboBox.prefHeight(width); + } + + @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().prefWidth(height); + } + + @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().prefHeight(width); + } + + + + /************************************************************************** + * + * Implementation + * + **************************************************************************/ + + private String buildString() { + final StringBuilder sb = new StringBuilder(); + for (int i = 0, max = selectedItems.size(); i < max; i++) { + T item = selectedItems.get(i); + if (control.getConverter() == null) { + sb.append(item); + } else { + sb.append(control.getConverter().toString(item)); + } + if (i < max - 1) { + sb.append(", "); //$NON-NLS-1$ + } + } + return sb.toString(); + } + + + /************************************************************************** + * + * Support classes / enums + * + **************************************************************************/ + +} diff --git a/src/impl/org/controlsfx/skin/CustomTextFieldSkin.java b/src/impl/org/controlsfx/skin/CustomTextFieldSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..00aa8f699a890e452537aaeec7f5b76ff59b5f6e --- /dev/null +++ b/src/impl/org/controlsfx/skin/CustomTextFieldSkin.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2013, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.beans.property.ObjectProperty; +import javafx.css.PseudoClass; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import javafx.scene.layout.StackPane; + +import com.sun.javafx.scene.control.behavior.TextFieldBehavior; +import com.sun.javafx.scene.control.skin.TextFieldSkin; +import com.sun.javafx.scene.text.HitInfo; + +public abstract class CustomTextFieldSkin extends TextFieldSkin { + + private static final PseudoClass HAS_NO_SIDE_NODE = PseudoClass.getPseudoClass("no-side-nodes"); //$NON-NLS-1$ + private static final PseudoClass HAS_LEFT_NODE = PseudoClass.getPseudoClass("left-node-visible"); //$NON-NLS-1$ + private static final PseudoClass HAS_RIGHT_NODE = PseudoClass.getPseudoClass("right-node-visible"); //$NON-NLS-1$ + + private Node left; + private StackPane leftPane; + private Node right; + private StackPane rightPane; + + private final TextField control; + + public CustomTextFieldSkin(final TextField control) { + super(control, new TextFieldBehavior(control)); + + this.control = control; + updateChildren(); + + registerChangeListener(leftProperty(), "LEFT_NODE"); //$NON-NLS-1$ + registerChangeListener(rightProperty(), "RIGHT_NODE"); //$NON-NLS-1$ + registerChangeListener(control.focusedProperty(), "FOCUSED"); //$NON-NLS-1$ + } + + public abstract ObjectProperty<Node> leftProperty(); + public abstract ObjectProperty<Node> rightProperty(); + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if (p == "LEFT_NODE" || p == "RIGHT_NODE") { //$NON-NLS-1$ //$NON-NLS-2$ + updateChildren(); + } + } + + private void updateChildren() { + Node newLeft = leftProperty().get(); + if (newLeft != null) { + getChildren().remove(leftPane); + leftPane = new StackPane(newLeft); + leftPane.setAlignment(Pos.CENTER_LEFT); + leftPane.getStyleClass().add("left-pane"); //$NON-NLS-1$ + getChildren().add(leftPane); + left = newLeft; + } + + Node newRight = rightProperty().get(); + if (newRight != null) { + getChildren().remove(rightPane); + rightPane = new StackPane(newRight); + rightPane.setAlignment(Pos.CENTER_RIGHT); + rightPane.getStyleClass().add("right-pane"); //$NON-NLS-1$ + getChildren().add(rightPane); + right = newRight; + } + + control.pseudoClassStateChanged(HAS_LEFT_NODE, left != null); + control.pseudoClassStateChanged(HAS_RIGHT_NODE, right != null); + control.pseudoClassStateChanged(HAS_NO_SIDE_NODE, left == null && right == null); + } + + @Override protected void layoutChildren(double x, double y, double w, double h) { + final double fullHeight = h + snappedTopInset() + snappedBottomInset(); + + final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.prefWidth(fullHeight)); + final double rightWidth = rightPane == null ? 0.0 : snapSize(rightPane.prefWidth(fullHeight)); + + final double textFieldStartX = snapPosition(x) + snapSize(leftWidth); + final double textFieldWidth = w - snapSize(leftWidth) - snapSize(rightWidth); + + super.layoutChildren(textFieldStartX, 0, textFieldWidth, fullHeight); + + if (leftPane != null) { + final double leftStartX = 0; + leftPane.resizeRelocate(leftStartX, 0, leftWidth, fullHeight); + } + + if (rightPane != null) { + final double rightStartX = rightPane == null ? 0.0 : w - rightWidth + snappedLeftInset(); + rightPane.resizeRelocate(rightStartX, 0, rightWidth, fullHeight); + } + } + + @Override + public HitInfo getIndex(double x, double y) { + /** + * This resolves https://bitbucket.org/controlsfx/controlsfx/issue/476 + * when we have a left Node and the click point is badly returned + * because we weren't considering the shift induced by the leftPane. + */ + final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.prefWidth(getSkinnable().getHeight())); + return super.getIndex(x - leftWidth, y); + } + + @Override + protected double computePrefWidth(double h, double topInset, double rightInset, double bottomInset, double leftInset) { + final double pw = super.computePrefWidth(h, topInset, rightInset, bottomInset, leftInset); + final double leftWidth = leftPane == null ? 0.0 : snapSize(leftPane.prefWidth(h)); + final double rightWidth = rightPane == null ? 0.0 : snapSize(rightPane.prefWidth(h)); + + return pw + leftWidth + rightWidth; + } + + @Override + protected double computePrefHeight(double w, double topInset, double rightInset, double bottomInset, double leftInset) { + final double ph = super.computePrefHeight(w, topInset, rightInset, bottomInset, leftInset); + final double leftHeight = leftPane == null ? 0.0 : snapSize(leftPane.prefHeight(-1)); + final double rightHeight = rightPane == null ? 0.0 : snapSize(rightPane.prefHeight(-1)); + + return Math.max(ph, Math.max(leftHeight, rightHeight)); + } +// +// @Override +// protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { +// return computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); +//} +} diff --git a/src/impl/org/controlsfx/skin/DecorationPane.java b/src/impl/org/controlsfx/skin/DecorationPane.java new file mode 100644 index 0000000000000000000000000000000000000000..eb8cf8768c95ff703b2d38fe28bf8be48913bbfb --- /dev/null +++ b/src/impl/org/controlsfx/skin/DecorationPane.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.layout.StackPane; + +import org.controlsfx.control.decoration.Decoration; +import org.controlsfx.control.decoration.Decorator; + +public class DecorationPane extends StackPane { + + // maps from a node to a list of its decoration nodes + private final Map<Node, List<Node>> nodeDecorationMap = new WeakHashMap<>(); + + ChangeListener<Boolean> visibilityListener = new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> o, Boolean wasVisible, Boolean isVisible) { + BooleanProperty p = (BooleanProperty)o; + Node n = (Node) p.getBean(); + + removeAllDecorationsOnNode(n, Decorator.getDecorations(n)); + Decorator.removeAllDecorations(n); + } + }; + + public DecorationPane() { + // Make DecorationPane transparent + setBackground(null); + } + + public void setRoot(Node root) { + getChildren().setAll(root); + } + + public void updateDecorationsOnNode(Node targetNode, List<Decoration> added, List<Decoration> removed) { + removeAllDecorationsOnNode(targetNode, removed); + addAllDecorationsOnNode(targetNode, added); + } + + private void showDecoration(Node targetNode, Decoration decoration) { + Node decorationNode = decoration.applyDecoration(targetNode); + if (decorationNode != null) { + List<Node> decorationNodes = nodeDecorationMap.get(targetNode); + if (decorationNodes == null) { + decorationNodes = new ArrayList<>(); + nodeDecorationMap.put(targetNode, decorationNodes); + } + decorationNodes.add(decorationNode); + + if (!getChildren().contains(decorationNode)) { + getChildren().add(decorationNode); + StackPane.setAlignment(decorationNode, Pos.TOP_LEFT); // TODO support for all positions. + } + } + + targetNode.visibleProperty().addListener(visibilityListener); + } + + private void removeAllDecorationsOnNode(Node targetNode, List<Decoration> decorations) { + if (decorations == null || targetNode == null) return; + + // We need to do two things: + // 1) Remove the decoration node (if it exists) from the nodeDecorationMap + // for the targetNode, if it exists. + List<Node> decorationNodes = nodeDecorationMap.remove(targetNode); + if (decorationNodes != null) { + for (Node decorationNode : decorationNodes) { + boolean success = getChildren().remove(decorationNode); + if (! success) { + throw new IllegalStateException("Could not remove decoration " + //$NON-NLS-1$ + decorationNode + " from decoration pane children list: " + //$NON-NLS-1$ + getChildren()); + } + } + } + + // 2) Tell the decoration to remove itself from the target node (if necessary) + for (Decoration decoration : decorations) { + decoration.removeDecoration(targetNode); + } + } + + private void addAllDecorationsOnNode(Node targetNode, List<Decoration> decorations) { + if (decorations == null) return; + for (Decoration decoration : decorations) { + showDecoration(targetNode, decoration); + } + } +} diff --git a/src/impl/org/controlsfx/skin/ExpandableTableRowSkin.java b/src/impl/org/controlsfx/skin/ExpandableTableRowSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..a8b5f8328103c6d6df4bf721f67931f013c17585 --- /dev/null +++ b/src/impl/org/controlsfx/skin/ExpandableTableRowSkin.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import com.sun.javafx.scene.control.skin.TableRowSkin; +import javafx.scene.Node; +import javafx.scene.control.TableRow; +import org.controlsfx.control.table.TableRowExpanderColumn; + +/** + * This skin is installed when you assign a {@link org.controlsfx.control.table.TableRowExpanderColumn} to a TableView. + * The skin will render the expanded node produced by the + * {@link org.controlsfx.control.table.TableRowExpanderColumn#expandedNodeCallback} whenever the expanded state is + * changed to true for a certain row. + * + * @param <S> The type of items in the TableRow + */ +public class ExpandableTableRowSkin<S> extends TableRowSkin<S> { + private final TableRow<S> tableRow; + private TableRowExpanderColumn<S> expander; + private Double tableRowPrefHeight = -1D; + + /** + * Create the ExpandableTableRowSkin and listen to changes for the item this table row represents. When the + * item is changed, the old expanded node, if any, is removed from the children list of the TableRow. + * + * @param tableRow The table row to apply this skin for + * @param expander The expander column, used to retrieve the expanded node when this row is expanded + */ + public ExpandableTableRowSkin(TableRow<S> tableRow, TableRowExpanderColumn<S> expander) { + super(tableRow); + this.tableRow = tableRow; + this.expander = expander; + tableRow.itemProperty().addListener((observable, oldValue, newValue) -> { + if (oldValue != null) { + Node expandedNode = this.expander.getExpandedNode(oldValue); + if (expandedNode != null) getChildren().remove(expandedNode); + } + }); + } + + /** + * Create the expanded content node that should represent the current table row. + * + * If the expanded content node is not currently in the children list of the TableRow it is automatically added. + * + * @return The expanded content Node + */ + private Node getContent() { + Node node = expander.getOrCreateExpandedNode(tableRow); + if (!getChildren().contains(node)) getChildren().add(node); + return node; + } + + /** + * Check if the current node is expanded. This is done by checking that there is an item for the current row, + * and that the expanded property for the row is true. + * + * @return A boolean indicating the expanded state of this row + */ + private Boolean isExpanded() { + return getSkinnable().getItem() != null && expander.getCellData(getSkinnable().getIndex()); + } + + /** + * Add the preferred height of the expanded Node whenever the expanded flag is true. + * + * @return The preferred height of the TableRow, appended with the preferred height of the expanded node + * if this row is currently expanded. + */ + @Override + protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + tableRowPrefHeight = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); + return isExpanded() ? tableRowPrefHeight + getContent().prefHeight(width) : tableRowPrefHeight; + } + + /** + * Lay out the columns of the TableRow, then add the expanded content node below if this row is currently expanded. + */ + @Override + protected void layoutChildren(double x, double y, double w, double h) { + super.layoutChildren(x, y, w, h); + if (isExpanded()) getContent().resizeRelocate(0.0, tableRowPrefHeight, w, h - tableRowPrefHeight); + } +} diff --git a/src/impl/org/controlsfx/skin/GridCellSkin.java b/src/impl/org/controlsfx/skin/GridCellSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..db10ea75597e12f44f9f5f5105760102dad71da8 --- /dev/null +++ b/src/impl/org/controlsfx/skin/GridCellSkin.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import org.controlsfx.control.GridCell; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.CellSkinBase; + +public class GridCellSkin<T> extends CellSkinBase<GridCell<T>, BehaviorBase<GridCell<T>>> { + + public GridCellSkin(GridCell<T> control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + } + +} diff --git a/src/impl/org/controlsfx/skin/GridRow.java b/src/impl/org/controlsfx/skin/GridRow.java new file mode 100644 index 0000000000000000000000000000000000000000..548ee8e22adac8d761f4f2391a9a5453cc82d3e9 --- /dev/null +++ b/src/impl/org/controlsfx/skin/GridRow.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import org.controlsfx.control.GridView; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.Skin; + +/** + * A GridRow is a container for {@link GridCell}, and represents a single + * row inside a {@link GridView}. + */ +class GridRow<T> extends IndexedCell<T>{ + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * + */ + public GridRow() { + super(); + getStyleClass().add("grid-row"); //$NON-NLS-1$ + + // we need to do this (or something similar) to allow for mouse wheel + // scrolling, as the GridRow has to report that it is non-empty (which + // is the second argument going into updateItem). + indexProperty().addListener(new InvalidationListener() { + @Override public void invalidated(Observable observable) { + updateItem(null, getIndex() == -1); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new GridRowSkin<>(this); + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + /** + * The {@link GridView} that this GridRow exists within. + */ + public SimpleObjectProperty<GridView<T>> gridViewProperty() { + return gridView; + } + private final SimpleObjectProperty<GridView<T>> gridView = + new SimpleObjectProperty<>(this, "gridView"); //$NON-NLS-1$ + + /** + * Sets the {@link GridView} that this GridRow exists within. + */ + public final void updateGridView(GridView<T> gridView) { + this.gridView.set(gridView); + } + + /** + * Returns the {@link GridView} that this GridRow exists within. + */ + public GridView<T> getGridView() { + return gridView.get(); + } +} diff --git a/src/impl/org/controlsfx/skin/GridRowSkin.java b/src/impl/org/controlsfx/skin/GridRowSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..2b03097a7b329c007da3af44217e5cd80d9f2ef4 --- /dev/null +++ b/src/impl/org/controlsfx/skin/GridRowSkin.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import javafx.scene.Node; + +import org.controlsfx.control.GridCell; +import org.controlsfx.control.GridView; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.CellSkinBase; + +public class GridRowSkin<T> extends CellSkinBase<GridRow<T>, BehaviorBase<GridRow<T>>> { + + public GridRowSkin(GridRow<T> control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + // Remove any children before creating cells (by default a LabeledText exist and we don't need it) + getChildren().clear(); + updateCells(); + + registerChangeListener(getSkinnable().indexProperty(), "INDEX"); //$NON-NLS-1$ + registerChangeListener(getSkinnable().widthProperty(), "WIDTH"); //$NON-NLS-1$ + registerChangeListener(getSkinnable().heightProperty(), "HEIGHT"); //$NON-NLS-1$ + } + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if ("INDEX".equals(p)) { //$NON-NLS-1$ + updateCells(); + } else if ("WIDTH".equals(p)) { //$NON-NLS-1$ + updateCells(); + } else if ("HEIGHT".equals(p)) { //$NON-NLS-1$ + updateCells(); + } + } + + /** + * Returns a cell element at a desired index + * @param index The index of the wanted cell element + * @return Cell element if exist else null + */ + @SuppressWarnings("unchecked") + public GridCell<T> getCellAtIndex(int index) { + if( index < getChildren().size() ) { + return (GridCell<T>)getChildren().get(index); + } + return null; + } + + /** + * Update all cells + * <p>Cells are only created when needed and re-used when possible.</p> + */ + public void updateCells() { + int rowIndex = getSkinnable().getIndex(); + if (rowIndex >= 0) { + GridView<T> gridView = getSkinnable().getGridView(); + int maxCellsInRow = ((GridViewSkin<?>)gridView.getSkin()).computeMaxCellsInRow(); + int totalCellsInGrid = gridView.getItems().size(); + int startCellIndex = rowIndex * maxCellsInRow; + int endCellIndex = startCellIndex + maxCellsInRow - 1; + int cacheIndex = 0; + + for (int cellIndex = startCellIndex; cellIndex <= endCellIndex; cellIndex++, cacheIndex++) { + if (cellIndex < totalCellsInGrid) { + // Check if we can re-use a cell at this index or create a new one + GridCell<T> cell = getCellAtIndex(cacheIndex); + if( cell == null ) { + cell = createCell(); + getChildren().add(cell); + } + cell.updateIndex(-1); + cell.updateIndex(cellIndex); + } + // we are going out of bounds -> exist the loop + else { break; } + } + + // In case we are re-using a row that previously had more cells than + // this one, we need to remove the extra cells that remain + getChildren().remove(cacheIndex, getChildren().size()); + } + } + + private GridCell<T> createCell() { + GridView<T> gridView = getSkinnable().gridViewProperty().get(); + GridCell<T> cell; + if (gridView.getCellFactory() != null) { + cell = gridView.getCellFactory().call(gridView); + } else { + cell = createDefaultCellImpl(); + } + cell.updateGridView(gridView); + return cell; + } + + private GridCell<T> createDefaultCellImpl() { + return new GridCell<T>() { + @Override protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if(empty) { + setText(""); //$NON-NLS-1$ + } else { + setText(item.toString()); + } + } + }; + } + + @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); + } + + @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return Double.MAX_VALUE; + } + + @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + GridView<T> gv = getSkinnable().gridViewProperty().get(); + return gv.getCellHeight() + gv.getVerticalCellSpacing() * 2; + } + + @Override protected void layoutChildren(double x, double y, double w, double h) { +// double currentWidth = getSkinnable().getWidth(); + double cellWidth = getSkinnable().gridViewProperty().get().getCellWidth(); + double cellHeight = getSkinnable().gridViewProperty().get().getCellHeight(); + double horizontalCellSpacing = getSkinnable().gridViewProperty().get().getHorizontalCellSpacing(); + double verticalCellSpacing = getSkinnable().gridViewProperty().get().getVerticalCellSpacing(); + + double xPos = 0; + double yPos = 0; + + // This has been commented out as I removed the API from GridView until + // a use case was created. +// HPos currentHorizontalAlignment = getSkinnable().gridViewProperty().get().getHorizontalAlignment(); +// if (currentHorizontalAlignment != null) { +// if (currentHorizontalAlignment.equals(HPos.CENTER)) { +// xPos = (currentWidth % computeCellWidth()) / 2; +// } else if (currentHorizontalAlignment.equals(HPos.RIGHT)) { +// xPos = currentWidth % computeCellWidth(); +// } +// } + + for (Node child : getChildren()) { + child.relocate(xPos + horizontalCellSpacing, yPos + verticalCellSpacing); + child.resize(cellWidth, cellHeight); + xPos = xPos + horizontalCellSpacing + cellWidth + horizontalCellSpacing; + } + } +} diff --git a/src/impl/org/controlsfx/skin/GridViewSkin.java b/src/impl/org/controlsfx/skin/GridViewSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..8eea039df1e266c59923615cd2db6ec2cb3450e7 --- /dev/null +++ b/src/impl/org/controlsfx/skin/GridViewSkin.java @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.WeakListChangeListener; +import javafx.util.Callback; + +import org.controlsfx.control.GridView; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.VirtualContainerBase; +import com.sun.javafx.scene.control.skin.VirtualFlow; + +public class GridViewSkin<T> extends VirtualContainerBase<GridView<T>, BehaviorBase<GridView<T>>, GridRow<T>> { + + private final ListChangeListener<T> gridViewItemsListener = new ListChangeListener<T>() { + @Override public void onChanged(ListChangeListener.Change<? extends T> change) { + updateRowCount(); + getSkinnable().requestLayout(); + } + }; + + private final WeakListChangeListener<T> weakGridViewItemsListener = new WeakListChangeListener<>(gridViewItemsListener); + + @SuppressWarnings("rawtypes") + public GridViewSkin(GridView<T> control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding>emptyList())); + + updateGridViewItems(); + + flow.setId("virtual-flow"); //$NON-NLS-1$ + flow.setPannable(false); + flow.setVertical(true); + flow.setFocusTraversable(getSkinnable().isFocusTraversable()); + flow.setCreateCell(new Callback<VirtualFlow, GridRow<T>>() { + @Override public GridRow<T> call(VirtualFlow flow) { + return GridViewSkin.this.createCell(); + } + }); + getChildren().add(flow); + + updateRowCount(); + + // Register listeners + registerChangeListener(control.itemsProperty(), "ITEMS"); //$NON-NLS-1$ + registerChangeListener(control.cellFactoryProperty(), "CELL_FACTORY"); //$NON-NLS-1$ + registerChangeListener(control.parentProperty(), "PARENT"); //$NON-NLS-1$ + registerChangeListener(control.cellHeightProperty(), "CELL_HEIGHT"); //$NON-NLS-1$ + registerChangeListener(control.cellWidthProperty(), "CELL_WIDTH"); //$NON-NLS-1$ + registerChangeListener(control.horizontalCellSpacingProperty(), "HORIZONZAL_CELL_SPACING"); //$NON-NLS-1$ + registerChangeListener(control.verticalCellSpacingProperty(), "VERTICAL_CELL_SPACING"); //$NON-NLS-1$ + registerChangeListener(control.widthProperty(), "WIDTH_PROPERTY"); //$NON-NLS-1$ + registerChangeListener(control.heightProperty(), "HEIGHT_PROPERTY"); //$NON-NLS-1$ + } + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + if (p == "ITEMS") { //$NON-NLS-1$ + updateGridViewItems(); + } else if (p == "CELL_FACTORY") { //$NON-NLS-1$ + flow.recreateCells(); + } else if (p == "CELL_HEIGHT") { //$NON-NLS-1$ + flow.recreateCells(); + } else if (p == "CELL_WIDTH") { //$NON-NLS-1$ + updateRowCount(); + flow.recreateCells(); + } else if (p == "HORIZONZAL_CELL_SPACING") { //$NON-NLS-1$ + updateRowCount(); + flow.recreateCells(); + } else if (p == "VERTICAL_CELL_SPACING") { //$NON-NLS-1$ + flow.recreateCells(); + } else if (p == "PARENT") { //$NON-NLS-1$ + if (getSkinnable().getParent() != null && getSkinnable().isVisible()) { + getSkinnable().requestLayout(); + } + } else if (p == "WIDTH_PROPERTY" || p == "HEIGHT_PROPERTY") { //$NON-NLS-1$ //$NON-NLS-2$ + updateRowCount(); + } + } + + // Added by Fanis!!! + public VirtualFlow<GridRow<T>> getFlow() { + return flow; + } + + public void updateGridViewItems() { + if (getSkinnable().getItems() != null) { + getSkinnable().getItems().removeListener(weakGridViewItemsListener); + } + + if (getSkinnable().getItems() != null) { + getSkinnable().getItems().addListener(weakGridViewItemsListener); + } + + updateRowCount(); + flow.recreateCells(); + getSkinnable().requestLayout(); + } + + @Override protected void updateRowCount() { + if (flow == null) + return; + + int oldCount = flow.getCellCount(); + int newCount = getItemCount(); + + if (newCount != oldCount) { + flow.setCellCount(newCount); + flow.rebuildCells(); + } else { + flow.reconfigureCells(); + } + updateRows(newCount); + } + + @Override protected void layoutChildren(double x, double y, double w, double h) { + double x1 = getSkinnable().getInsets().getLeft(); + double y1 = getSkinnable().getInsets().getTop(); + double w1 = getSkinnable().getWidth() - (getSkinnable().getInsets().getLeft() + getSkinnable().getInsets().getRight()); + double h1 = getSkinnable().getHeight() - (getSkinnable().getInsets().getTop() + getSkinnable().getInsets().getBottom()); + + flow.resizeRelocate(x1, y1, w1, h1); + } + + @Override public GridRow<T> createCell() { + GridRow<T> row = new GridRow<>(); + row.updateGridView(getSkinnable()); + return row; + } + + /** + * Returns the number of row needed to display the whole set of cells + * @return GridView row count + */ + @Override public int getItemCount() { + final ObservableList<?> items = getSkinnable().getItems(); + // Fix for #98 : int division should be cast to get the result as + // double and ceiled to get the max int of it (as we are looking for + // the max number of necessary row) + return items == null ? 0 : (int)Math.ceil((double)items.size() / computeMaxCellsInRow()); + } + + /** + * Returns the max number of cell per row + * @return Max cell number per row + */ + public int computeMaxCellsInRow() { + return Math.max((int) Math.floor(computeRowWidth() / computeCellWidth()), 1); + } + + /** + * Returns the width of a row + * (should be GridView.width - GridView.Scrollbar.width) + * @return Computed width of a row + */ + protected double computeRowWidth() { + // Fix for #98 : width calculation should take the scrollbar size + // into account + + // TODO: need to figure out how to get the real scrollbar width and + // replace the 18 value + return getSkinnable().getWidth() - 18; + } + + /** + * Returns the width of a cell + * @return Computed width of a cell + */ + protected double computeCellWidth() { + return getSkinnable().cellWidthProperty().doubleValue() + (getSkinnable().horizontalCellSpacingProperty().doubleValue() * 2); + } + + protected void updateRows(int rowCount) { + for (int i = 0; i < rowCount; i++) { + GridRow<T> row = flow.getVisibleCell(i); + if (row != null) { + // FIXME hacky - need to better understand what this is about + row.updateIndex(-1); + row.updateIndex(i); + } + } + } + + protected boolean areRowsVisible() { + if (flow == null) + return false; + + if (flow.getFirstVisibleCell() == null) + return false; + + if (flow.getLastVisibleCell() == null) + return false; + + return true; + } + + @Override protected double computeMinHeight(double height, double topInset, double rightInset, double bottomInset, + double leftInset) { + return 0; + } +} diff --git a/src/impl/org/controlsfx/skin/HiddenSidesPaneSkin.java b/src/impl/org/controlsfx/skin/HiddenSidesPaneSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..bd040bdf80b2b08f0547d440d51b6847c03663dc --- /dev/null +++ b/src/impl/org/controlsfx/skin/HiddenSidesPaneSkin.java @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.animation.Animation.Status; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.InvalidationListener; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.event.EventHandler; +import javafx.geometry.Side; +import javafx.scene.Node; +import javafx.scene.control.SkinBase; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; +import javafx.util.Duration; + +import org.controlsfx.control.HiddenSidesPane; + +public class HiddenSidesPaneSkin extends SkinBase<HiddenSidesPane> { + + private final StackPane stackPane; + private final EventHandler<MouseEvent> exitedHandler; + private boolean mousePressed; + + public HiddenSidesPaneSkin(HiddenSidesPane pane) { + super(pane); + + exitedHandler = event -> { + if (isMouseEnabled() && getSkinnable().getPinnedSide() == null + && !mousePressed) { + hide(); + } + }; + + stackPane = new StackPane(); + getChildren().add(stackPane); + updateStackPane(); + + InvalidationListener rebuildListener = observable -> updateStackPane(); + pane.contentProperty().addListener(rebuildListener); + pane.topProperty().addListener(rebuildListener); + pane.rightProperty().addListener(rebuildListener); + pane.bottomProperty().addListener(rebuildListener); + pane.leftProperty().addListener(rebuildListener); + + pane.addEventFilter(MouseEvent.MOUSE_MOVED, event -> { + if (isMouseEnabled() && getSkinnable().getPinnedSide() == null) { + Side side = getSide(event); + if (side != null) { + show(side); + } else if (isMouseMovedOutsideSides(event)) { + hide(); + } + } + }); + + pane.addEventFilter(MouseEvent.MOUSE_EXITED, exitedHandler); + + pane.addEventFilter(MouseEvent.MOUSE_PRESSED, + event -> mousePressed = true); + + pane.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> { + mousePressed = false; + + if (isMouseEnabled() && getSkinnable().getPinnedSide() == null) { + Side side = getSide(event); + if (side != null) { + show(side); + } else { + hide(); + } + } + }); + + for (Side side : Side.values()) { + visibility[side.ordinal()] = new SimpleDoubleProperty(0); + visibility[side.ordinal()].addListener(observable -> getSkinnable() + .requestLayout()); + } + + Side pinnedSide = getSkinnable().getPinnedSide(); + if (pinnedSide != null) { + show(pinnedSide); + } + + pane.pinnedSideProperty().addListener( + observable -> show(getSkinnable().getPinnedSide())); + + Rectangle clip = new Rectangle(); + clip.setX(0); + clip.setY(0); + clip.widthProperty().bind(getSkinnable().widthProperty()); + clip.heightProperty().bind(getSkinnable().heightProperty()); + + getSkinnable().setClip(clip); + } + + private boolean isMouseMovedOutsideSides(MouseEvent event) { + if (getSkinnable().getLeft() != null + && getSkinnable().getLeft().getBoundsInParent() + .contains(event.getX(), event.getY())) { + return false; + } + + if (getSkinnable().getTop() != null + && getSkinnable().getTop().getBoundsInParent() + .contains(event.getX(), event.getY())) { + return false; + } + + if (getSkinnable().getRight() != null + && getSkinnable().getRight().getBoundsInParent() + .contains(event.getX(), event.getY())) { + return false; + } + + if (getSkinnable().getBottom() != null + && getSkinnable().getBottom().getBoundsInParent() + .contains(event.getX(), event.getY())) { + return false; + } + + return true; + } + + private boolean isMouseEnabled() { + return getSkinnable().getTriggerDistance() > 0; + } + + private Side getSide(MouseEvent evt) { + if (stackPane.getBoundsInLocal().contains(evt.getX(), evt.getY())) { + double trigger = getSkinnable().getTriggerDistance(); + if (evt.getX() <= trigger) { + return Side.LEFT; + } else if (evt.getX() > getSkinnable().getWidth() - trigger) { + return Side.RIGHT; + } else if (evt.getY() <= trigger) { + return Side.TOP; + } else if (evt.getY() > getSkinnable().getHeight() - trigger) { + return Side.BOTTOM; + } + } + + return null; + } + + private DoubleProperty[] visibility = new SimpleDoubleProperty[Side + .values().length]; + + private Timeline showTimeline; + + private void show(Side side) { + if (hideTimeline != null) { + hideTimeline.stop(); + } + + if (showTimeline != null && showTimeline.getStatus() == Status.RUNNING) { + return; + } + + KeyValue[] keyValues = new KeyValue[Side.values().length]; + for (Side s : Side.values()) { + keyValues[s.ordinal()] = new KeyValue(visibility[s.ordinal()], + s.equals(side) ? 1 : 0); + } + + Duration delay = getSkinnable().getAnimationDelay() != null ? getSkinnable() + .getAnimationDelay() : Duration.millis(300); + Duration duration = getSkinnable().getAnimationDuration() != null ? getSkinnable() + .getAnimationDuration() : Duration.millis(200); + + KeyFrame keyFrame = new KeyFrame(duration, keyValues); + showTimeline = new Timeline(keyFrame); + showTimeline.setDelay(delay); + showTimeline.play(); + } + + private Timeline hideTimeline; + + private void hide() { + if (showTimeline != null) { + showTimeline.stop(); + } + + if (hideTimeline != null && hideTimeline.getStatus() == Status.RUNNING) { + return; + } + + boolean sideVisible = false; + for (Side side : Side.values()) { + if (visibility[side.ordinal()].get() > 0) { + sideVisible = true; + break; + } + } + + // nothing to do here + if (!sideVisible) { + return; + } + + KeyValue[] keyValues = new KeyValue[Side.values().length]; + for (Side side : Side.values()) { + keyValues[side.ordinal()] = new KeyValue( + visibility[side.ordinal()], 0); + } + + Duration delay = getSkinnable().getAnimationDelay() != null ? getSkinnable() + .getAnimationDelay() : Duration.millis(300); + Duration duration = getSkinnable().getAnimationDuration() != null ? getSkinnable() + .getAnimationDuration() : Duration.millis(200); + + KeyFrame keyFrame = new KeyFrame(duration, keyValues); + hideTimeline = new Timeline(keyFrame); + hideTimeline.setDelay(delay); + hideTimeline.play(); + } + + private void updateStackPane() { + stackPane.getChildren().clear(); + + if (getSkinnable().getContent() != null) { + stackPane.getChildren().add(getSkinnable().getContent()); + } + if (getSkinnable().getTop() != null) { + stackPane.getChildren().add(getSkinnable().getTop()); + getSkinnable().getTop().setManaged(false); + getSkinnable().getTop().removeEventFilter(MouseEvent.MOUSE_EXITED, + exitedHandler); + getSkinnable().getTop().addEventFilter(MouseEvent.MOUSE_EXITED, + exitedHandler); + } + if (getSkinnable().getRight() != null) { + stackPane.getChildren().add(getSkinnable().getRight()); + getSkinnable().getRight().setManaged(false); + getSkinnable().getRight().removeEventFilter( + MouseEvent.MOUSE_EXITED, exitedHandler); + getSkinnable().getRight().addEventFilter(MouseEvent.MOUSE_EXITED, + exitedHandler); + } + if (getSkinnable().getBottom() != null) { + stackPane.getChildren().add(getSkinnable().getBottom()); + getSkinnable().getBottom().setManaged(false); + getSkinnable().getBottom().removeEventFilter( + MouseEvent.MOUSE_EXITED, exitedHandler); + getSkinnable().getBottom().addEventFilter(MouseEvent.MOUSE_EXITED, + exitedHandler); + } + if (getSkinnable().getLeft() != null) { + stackPane.getChildren().add(getSkinnable().getLeft()); + getSkinnable().getLeft().setManaged(false); + getSkinnable().getLeft().removeEventFilter(MouseEvent.MOUSE_EXITED, + exitedHandler); + getSkinnable().getLeft().addEventFilter(MouseEvent.MOUSE_EXITED, + exitedHandler); + } + } + + @Override + protected void layoutChildren(double contentX, double contentY, + double contentWidth, double contentHeight) { + + /* + * Layout the stackpane in a normal way (equals + * "lay out the content node", the only managed node) + */ + super.layoutChildren(contentX, contentY, contentWidth, contentHeight); + + // layout the unmanaged side nodes + + Node bottom = getSkinnable().getBottom(); + if (bottom != null) { + double prefHeight = bottom.prefHeight(-1); + double offset = prefHeight + * visibility[Side.BOTTOM.ordinal()].get(); + bottom.resizeRelocate(contentX, contentY + contentHeight - offset, + contentWidth, prefHeight); + bottom.setVisible(visibility[Side.BOTTOM.ordinal()].get() > 0); + } + + Node left = getSkinnable().getLeft(); + if (left != null) { + double prefWidth = left.prefWidth(-1); + double offset = prefWidth * visibility[Side.LEFT.ordinal()].get(); + left.resizeRelocate(contentX - (prefWidth - offset), contentY, + prefWidth, contentHeight); + left.setVisible(visibility[Side.LEFT.ordinal()].get() > 0); + } + + Node right = getSkinnable().getRight(); + if (right != null) { + double prefWidth = right.prefWidth(-1); + double offset = prefWidth * visibility[Side.RIGHT.ordinal()].get(); + right.resizeRelocate(contentX + contentWidth - offset, contentY, + prefWidth, contentHeight); + right.setVisible(visibility[Side.RIGHT.ordinal()].get() > 0); + } + + Node top = getSkinnable().getTop(); + if (top != null) { + double prefHeight = top.prefHeight(-1); + double offset = prefHeight * visibility[Side.TOP.ordinal()].get(); + top.resizeRelocate(contentX, contentY - (prefHeight - offset), + contentWidth, prefHeight); + top.setVisible(visibility[Side.TOP.ordinal()].get() > 0); + } + } +} diff --git a/src/impl/org/controlsfx/skin/HyperlinkLabelSkin.java b/src/impl/org/controlsfx/skin/HyperlinkLabelSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..10e668148191b5fe04a4ddbab07e5fff461a0470 --- /dev/null +++ b/src/impl/org/controlsfx/skin/HyperlinkLabelSkin.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +import org.controlsfx.control.HyperlinkLabel; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +public class HyperlinkLabelSkin extends BehaviorSkinBase<HyperlinkLabel, BehaviorBase<HyperlinkLabel>> { + + /*************************************************************************** + * + * Static fields + * + **************************************************************************/ + + // The strings used to delimit the hyperlinks + private static final String HYPERLINK_START = "["; //$NON-NLS-1$ + private static final String HYPERLINK_END = "]"; //$NON-NLS-1$ + + + + /*************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private final TextFlow textFlow; + private final EventHandler<ActionEvent> eventHandler = new EventHandler<ActionEvent>() { + @Override public void handle(final ActionEvent event) { + EventHandler<ActionEvent> onActionHandler = getSkinnable().getOnAction(); + if (onActionHandler != null) { + onActionHandler.handle(event); + } + } + }; + + + + /*************************************************************************** + * + * Constructors + * + **************************************************************************/ + + public HyperlinkLabelSkin(HyperlinkLabel control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + this.textFlow = new TextFlow(); + getChildren().add(textFlow); + updateText(); + + registerChangeListener(control.textProperty(), "TEXT"); //$NON-NLS-1$ + } + + + + /*************************************************************************** + * + * Implementation + * + **************************************************************************/ + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if (p == "TEXT") { //$NON-NLS-1$ + updateText(); + } + } + + // splits up the string into Text and Hyperlink nodes, and places them + // into a TextFlow instance + private void updateText() { + final String text = getSkinnable().getText(); + + if (text == null || text.isEmpty()) { + textFlow.getChildren().clear(); + return; + } + + // parse the text and put it into an array list + final List<Node> nodes = new ArrayList<>(); + + int start = 0; + final int textLength = text.length(); + while (start != -1 && start < textLength) { + int startPos = text.indexOf(HYPERLINK_START, start); + int endPos = text.indexOf(HYPERLINK_END, startPos); + + // if the startPos is -1, there are no more hyperlinks... + if (startPos == -1 || endPos == -1) { + if (textLength > start) { + // ...but there is still text to turn into one last label + Label label = new Label(text.substring(start)); + nodes.add(label); + break; + } + } + + // firstly, create a label from start to startPos + Text label = new Text(text.substring(start, startPos)); + nodes.add(label); + + // if endPos is greater than startPos, create a hyperlink + Hyperlink hyperlink = new Hyperlink(text.substring(startPos + 1, endPos)); + hyperlink.setPadding(new Insets(0, 0, 0, 0)); + hyperlink.setOnAction(eventHandler); + nodes.add(hyperlink); + + start = endPos + 1; + } + + textFlow.getChildren().setAll(nodes); + } +} diff --git a/src/impl/org/controlsfx/skin/InfoOverlaySkin.java b/src/impl/org/controlsfx/skin/InfoOverlaySkin.java new file mode 100644 index 0000000000000000000000000000000000000000..71bc6ad2aad399d22f74328e499dcca84827d260 --- /dev/null +++ b/src/impl/org/controlsfx/skin/InfoOverlaySkin.java @@ -0,0 +1,248 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import javafx.animation.Animation.Status; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.util.Duration; + +import org.controlsfx.control.InfoOverlay; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +public class InfoOverlaySkin extends BehaviorSkinBase<InfoOverlay, BehaviorBase<InfoOverlay>> { + + private final ImageView EXPAND_IMAGE = new ImageView(new Image(InfoOverlay.class.getResource("expand.png").toExternalForm())); //$NON-NLS-1$ + private final ImageView COLLAPSE_IMAGE = new ImageView(new Image(InfoOverlay.class.getResource("collapse.png").toExternalForm())); //$NON-NLS-1$ + + private static final Duration TRANSITION_DURATION = new Duration(350.0); + + private Node content; + private Label infoLabel; + private HBox infoPanel; + private ToggleButton expandCollapseButton; + + // animation support + private Timeline timeline; + private DoubleProperty transition = new SimpleDoubleProperty(this, "transition", 0.0) { //$NON-NLS-1$ + @Override protected void invalidated() { + getSkinnable().requestLayout(); + } + }; + + public InfoOverlaySkin(final InfoOverlay control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + // content + content = control.getContent(); + control.hoverProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> o, Boolean wasHover, Boolean isHover) { + if (control.isShowOnHover()) { + if ((isHover && ! isExpanded()) || (!isHover && isExpanded())) { + doToggle(); + } + } + } + }); + + // text + infoLabel = new Label(); + infoLabel.setWrapText(true); + infoLabel.setAlignment(Pos.TOP_LEFT); + infoLabel.getStyleClass().add("info"); //$NON-NLS-1$ + infoLabel.textProperty().bind(control.textProperty()); + + // button to expand / collapse the info overlay + expandCollapseButton = new ToggleButton(); + expandCollapseButton.setMouseTransparent(true); + expandCollapseButton.visibleProperty().bind(Bindings.not(control.showOnHoverProperty())); + expandCollapseButton.managedProperty().bind(Bindings.not(control.showOnHoverProperty())); + updateToggleButton(); + + // container for the info overlay and the button + infoPanel = new HBox(infoLabel, expandCollapseButton); + infoPanel.setAlignment(Pos.TOP_LEFT); + infoPanel.setFillHeight(true); + infoPanel.getStyleClass().add("info-panel"); //$NON-NLS-1$ + infoPanel.setCursor(Cursor.HAND); + infoPanel.setOnMouseClicked(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + if (! control.isShowOnHover()) { + doToggle(); + } + } + }); + + // adding everything to the scenegraph + getChildren().addAll(content, infoPanel); + + registerChangeListener(control.contentProperty(), "CONTENT"); //$NON-NLS-1$ + } + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if ("CONTENT".equals(p)) { //$NON-NLS-1$ + getChildren().remove(0); + getChildren().add(0, getSkinnable().getContent()); + getSkinnable().requestLayout(); + } + } + + private void doToggle() { + // do animation to show / hide the info panel + expandCollapseButton.setSelected(!expandCollapseButton.isSelected()); + toggleInfoPanel(); + updateToggleButton(); + } + + private boolean isExpanded() { + return expandCollapseButton.isSelected(); + } + + @Override + protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { + final double contentPrefHeight = content.prefHeight(contentWidth); + + // we calculate the pref width of the expand/collapse button. We will + // ensure that the button does not get smaller than this. + final double toggleButtonPrefWidth = expandCollapseButton.prefWidth(-1); + expandCollapseButton.setMinWidth(toggleButtonPrefWidth); + + // All remaining width goes to the info label + final Insets infoPanelPadding = infoPanel.getPadding(); + final double infoLabelWidth = snapSize(contentWidth - toggleButtonPrefWidth - + infoPanelPadding.getLeft() - infoPanelPadding.getRight()); + + // we then can work out the necessary height for the info panel, based on + // whether it is expanded or not, and given the current state of the animation. + final double prefInfoPanelHeight = (snapSize(infoLabel.prefHeight(infoLabelWidth)) + + snapSpace(infoPanel.getPadding().getTop()) + + snapSpace(infoPanel.getPadding().getBottom())) * + transition.get(); + + infoLabel.setMaxWidth(infoLabelWidth); + infoLabel.setMaxHeight(prefInfoPanelHeight); + + // position the imageView + layoutInArea(content, contentX, contentY, + contentWidth, contentHeight, -1, HPos.CENTER, VPos.TOP); + + // position the infoPanel (the HBox consisting of the Label and ToggleButton) + layoutInArea(infoPanel, contentX, snapPosition(contentPrefHeight - prefInfoPanelHeight), + contentWidth, prefInfoPanelHeight, 0, HPos.CENTER, VPos.BOTTOM); + } + + private void updateToggleButton() { + if (expandCollapseButton.isSelected()) { + expandCollapseButton.getStyleClass().setAll("collapse-button"); //$NON-NLS-1$ + expandCollapseButton.setGraphic(COLLAPSE_IMAGE); + } else { + expandCollapseButton.getStyleClass().setAll("expand-button"); //$NON-NLS-1$ + expandCollapseButton.setGraphic(EXPAND_IMAGE); + } + } + + @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + double insets = topInset + bottomInset; + return insets + (content == null ? 0 : content.prefHeight(width)); + } + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + double insets = leftInset + rightInset; + return insets + (content == null ? 0 : content.prefWidth(height)); + } + + @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); + } + + @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); + } + + private void toggleInfoPanel() { + // animate! + // The best way I know how is to transition a number between 0.0 and 1.0 + // over a set duration, and have this request layout as it goes. Then, + // use this value and multiply it against the actualInfoPanelHeight + // variable in layoutChildren - this will give a nice smooth animation. + if (content == null) { + return; + } + + Duration duration; + if (timeline != null && (timeline.getStatus() != Status.STOPPED)) { + duration = timeline.getCurrentTime(); + timeline.stop(); + } else { + duration = TRANSITION_DURATION; + } + + timeline = new Timeline(); + timeline.setCycleCount(1); + + KeyFrame k1, k2; + + if (isExpanded()) { + k1 = new KeyFrame(Duration.ZERO, new KeyValue(transition, 0)); + k2 = new KeyFrame(duration,new KeyValue(transition, 1, Interpolator.LINEAR)); + } else { + k1 = new KeyFrame(Duration.ZERO, new KeyValue(transition, 1)); + k2 = new KeyFrame(duration, new KeyValue(transition, 0, Interpolator.LINEAR)); + } + + timeline.getKeyFrames().setAll(k1, k2); + timeline.play(); + } +} diff --git a/src/impl/org/controlsfx/skin/ListSelectionViewSkin.java b/src/impl/org/controlsfx/skin/ListSelectionViewSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..376fac01f7ae65508f527bde48e263d81283032f --- /dev/null +++ b/src/impl/org/controlsfx/skin/ListSelectionViewSkin.java @@ -0,0 +1,474 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import static java.util.Objects.requireNonNull; +import static javafx.scene.control.SelectionMode.MULTIPLE; +import static javafx.scene.input.MouseEvent.MOUSE_CLICKED; + +import java.util.ArrayList; +import java.util.List; + +import javafx.beans.InvalidationListener; +import javafx.beans.binding.Bindings; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.control.SkinBase; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.RowConstraints; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; + +import org.controlsfx.control.ListSelectionView; +import org.controlsfx.glyphfont.FontAwesome; + +public class ListSelectionViewSkin<T> extends SkinBase<ListSelectionView<T>> { + + private GridPane gridPane; + private final HBox horizontalButtonBox; + private final VBox verticalButtonBox; + private Button moveToTarget; + private Button moveToTargetAll; + private Button moveToSourceAll; + private Button moveToSource; + private ListView<T> sourceListView; + private ListView<T> targetListView; + + public ListSelectionViewSkin(ListSelectionView<T> view) { + super(view); + + sourceListView = requireNonNull(createSourceListView(), + "source list view can not be null"); + sourceListView.setId("source-list-view"); + sourceListView.setItems(view.getSourceItems()); + + targetListView = requireNonNull(createTargetListView(), + "target list view can not be null"); + targetListView.setId("target-list-view"); + targetListView.setItems(view.getTargetItems()); + + sourceListView.cellFactoryProperty().bind(view.cellFactoryProperty()); + targetListView.cellFactoryProperty().bind(view.cellFactoryProperty()); + + gridPane = createGridPane(); + horizontalButtonBox = createHorizontalButtonBox(); + verticalButtonBox = createVerticalButtonBox(); + + getChildren().add(gridPane); + + InvalidationListener updateListener = o -> updateView(); + + view.sourceHeaderProperty().addListener(updateListener); + view.sourceFooterProperty().addListener(updateListener); + view.targetHeaderProperty().addListener(updateListener); + view.targetFooterProperty().addListener(updateListener); + + updateView(); + + getSourceListView().addEventHandler( + MOUSE_CLICKED, + event -> { + if (event.getButton() == MouseButton.PRIMARY + && event.getClickCount() == 2) { + moveToTarget(); + } + }); + + getTargetListView().addEventHandler( + MOUSE_CLICKED, + event -> { + if (event.getButton() == MouseButton.PRIMARY + && event.getClickCount() == 2) { + moveToSource(); + } + }); + + view.orientationProperty().addListener(observable -> updateView()); + } + + private GridPane createGridPane() { + GridPane gridPane = new GridPane(); + gridPane.getStyleClass().add("grid-pane"); + + return gridPane; + } + + // Constraints used when view's orientation is HORIZONTAL + private void setHorizontalViewContraints() { + gridPane.getColumnConstraints().clear(); + gridPane.getRowConstraints().clear(); + + ColumnConstraints col1 = new ColumnConstraints(); + + col1.setFillWidth(true); + col1.setHgrow(Priority.ALWAYS); + col1.setMaxWidth(Double.MAX_VALUE); + col1.setPrefWidth(200); + + ColumnConstraints col2 = new ColumnConstraints(); + col2.setFillWidth(true); + col2.setHgrow(Priority.NEVER); + + ColumnConstraints col3 = new ColumnConstraints(); + col3.setFillWidth(true); + col3.setHgrow(Priority.ALWAYS); + col3.setMaxWidth(Double.MAX_VALUE); + col3.setPrefWidth(200); + + gridPane.getColumnConstraints().addAll(col1, col2, col3); + + RowConstraints row1 = new RowConstraints(); + row1.setFillHeight(true); + row1.setVgrow(Priority.NEVER); + + RowConstraints row2 = new RowConstraints(); + row2.setMaxHeight(Double.MAX_VALUE); + row2.setPrefHeight(200); + row2.setVgrow(Priority.ALWAYS); + + RowConstraints row3 = new RowConstraints(); + row3.setFillHeight(true); + row3.setVgrow(Priority.NEVER); + + gridPane.getRowConstraints().addAll(row1, row2, row3); + } + + // Constraints used when view's orientation is VERTICAL + private void setVerticalViewContraints() { + gridPane.getColumnConstraints().clear(); + gridPane.getRowConstraints().clear(); + + ColumnConstraints col1 = new ColumnConstraints(); + + col1.setFillWidth(true); + col1.setHgrow(Priority.ALWAYS); + col1.setMaxWidth(Double.MAX_VALUE); + col1.setPrefWidth(200); + + gridPane.getColumnConstraints().addAll(col1); + + RowConstraints row1 = new RowConstraints(); + row1.setFillHeight(true); + row1.setVgrow(Priority.NEVER); + + RowConstraints row2 = new RowConstraints(); + row2.setMaxHeight(Double.MAX_VALUE); + row2.setPrefHeight(200); + row2.setVgrow(Priority.ALWAYS); + + RowConstraints row3 = new RowConstraints(); + row3.setFillHeight(true); + row3.setVgrow(Priority.NEVER); + + RowConstraints row4 = new RowConstraints(); + row4.setFillHeight(true); + row4.setVgrow(Priority.NEVER); + + RowConstraints row5 = new RowConstraints(); + row5.setFillHeight(true); + row5.setVgrow(Priority.NEVER); + + RowConstraints row6 = new RowConstraints(); + row6.setMaxHeight(Double.MAX_VALUE); + row6.setPrefHeight(200); + row6.setVgrow(Priority.ALWAYS); + + RowConstraints row7 = new RowConstraints(); + row7.setFillHeight(true); + row7.setVgrow(Priority.NEVER); + + + gridPane.getRowConstraints().addAll(row1, row2, row3, row4, row5, row6, row7); + } + + // Used when view's orientation is HORIZONTAL + private VBox createVerticalButtonBox() { + VBox box = new VBox(5); + box.setFillWidth(true); + + FontAwesome fontAwesome = new FontAwesome(); + moveToTarget = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_RIGHT)); + moveToTargetAll = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_DOUBLE_RIGHT)); + + moveToSource = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_LEFT)); + moveToSourceAll = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_DOUBLE_LEFT)); + + updateButtons(); + + box.getChildren().addAll(moveToTarget, moveToTargetAll, moveToSource, + moveToSourceAll); + + return box; + } + + // Used when view's orientation is VERTICAL + private HBox createHorizontalButtonBox() { + HBox box = new HBox(5); + box.setFillHeight(true); + + FontAwesome fontAwesome = new FontAwesome(); + moveToTarget = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_DOWN)); + moveToTargetAll = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_DOUBLE_DOWN)); + + moveToSource = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_UP)); + moveToSourceAll = new Button("", + fontAwesome.create(FontAwesome.Glyph.ANGLE_DOUBLE_UP)); + + updateButtons(); + + box.getChildren().addAll(moveToTarget, moveToTargetAll, moveToSource, + moveToSourceAll); + + return box; + } + + private void updateButtons() { + + moveToTarget.getStyleClass().add("move-to-target-button"); + moveToTargetAll.getStyleClass().add("move-to-target-all-button"); + moveToSource.getStyleClass().add("move-to-source-button"); + moveToSourceAll.getStyleClass().add("move-to-source-all-button"); + + moveToTarget.setMaxWidth(Double.MAX_VALUE); + moveToTargetAll.setMaxWidth(Double.MAX_VALUE); + moveToSource.setMaxWidth(Double.MAX_VALUE); + moveToSourceAll.setMaxWidth(Double.MAX_VALUE); + + getSourceListView().itemsProperty().addListener( + it -> bindMoveAllButtonsToDataModel()); + + getTargetListView().itemsProperty().addListener( + it -> bindMoveAllButtonsToDataModel()); + + getSourceListView().selectionModelProperty().addListener( + it -> bindMoveButtonsToSelectionModel()); + + getTargetListView().selectionModelProperty().addListener( + it -> bindMoveButtonsToSelectionModel()); + + bindMoveButtonsToSelectionModel(); + bindMoveAllButtonsToDataModel(); + + moveToTarget.setOnAction(evt -> moveToTarget()); + + moveToTargetAll.setOnAction(evt -> moveToTargetAll()); + + moveToSource.setOnAction(evt -> moveToSource()); + + moveToSourceAll.setOnAction(evt -> moveToSourceAll()); + } + + private void bindMoveAllButtonsToDataModel() { + moveToTargetAll.disableProperty().bind( + Bindings.isEmpty(getSourceListView().getItems())); + + moveToSourceAll.disableProperty().bind( + Bindings.isEmpty(getTargetListView().getItems())); + } + + private void bindMoveButtonsToSelectionModel() { + moveToTarget.disableProperty().bind( + Bindings.isEmpty(getSourceListView().getSelectionModel() + .getSelectedItems())); + + moveToSource.disableProperty().bind( + Bindings.isEmpty(getTargetListView().getSelectionModel() + .getSelectedItems())); + } + + private void updateView() { + gridPane.getChildren().clear(); + + Node sourceHeader = getSkinnable().getSourceHeader(); + Node targetHeader = getSkinnable().getTargetHeader(); + Node sourceFooter = getSkinnable().getSourceFooter(); + Node targetFooter = getSkinnable().getTargetFooter(); + + ListView<T> sourceList = getSourceListView(); + ListView<T> targetList = getTargetListView(); + + StackPane stackPane = new StackPane(); + stackPane.setAlignment(Pos.CENTER); + + Orientation orientation = getSkinnable().getOrientation(); + + if (orientation == Orientation.HORIZONTAL) { + setHorizontalViewContraints(); + + if (sourceHeader != null) { + gridPane.add(sourceHeader, 0, 0); + } + + if (targetHeader != null) { + gridPane.add(targetHeader, 2, 0); + } + + if (sourceList != null) { + gridPane.add(sourceList, 0, 1); + } + + if (targetList != null) { + gridPane.add(targetList, 2, 1); + } + + if (sourceFooter != null) { + gridPane.add(sourceFooter, 0, 2); + } + + if (targetFooter != null) { + gridPane.add(targetFooter, 2, 2); + } + + stackPane.getChildren().add(verticalButtonBox); + gridPane.add(stackPane, 1, 1); + } else { + setVerticalViewContraints(); + + if (sourceHeader != null) { + gridPane.add(sourceHeader, 0, 0); + } + + if (targetHeader != null) { + gridPane.add(targetHeader, 0, 4); + } + + if (sourceList != null) { + gridPane.add(sourceList, 0, 1); + } + + if (targetList != null) { + gridPane.add(targetList, 0, 5); + } + + if (sourceFooter != null) { + gridPane.add(sourceFooter, 0, 2); + } + + if (targetFooter != null) { + gridPane.add(targetFooter, 0, 6); + } + + stackPane.getChildren().add(horizontalButtonBox); + gridPane.add(stackPane, 0, 3); + } + } + + private void moveToTarget() { + move(getSourceListView(), getTargetListView()); + getSourceListView().getSelectionModel().clearSelection(); + } + + private void moveToTargetAll() { + move(getSourceListView(), getTargetListView(), new ArrayList<>( + getSourceListView().getItems())); + getSourceListView().getSelectionModel().clearSelection(); + } + + private void moveToSource() { + move(getTargetListView(), getSourceListView()); + getTargetListView().getSelectionModel().clearSelection(); + } + + private void moveToSourceAll() { + move(getTargetListView(), getSourceListView(), new ArrayList<>( + getTargetListView().getItems())); + getTargetListView().getSelectionModel().clearSelection(); + } + + private void move(ListView<T> viewA, ListView<T> viewB) { + List<T> selectedItems = new ArrayList<>(viewA.getSelectionModel() + .getSelectedItems()); + move(viewA, viewB, selectedItems); + } + + private void move(ListView<T> viewA, ListView<T> viewB, List<T> items) { + for (T item : items) { + viewA.getItems().remove(item); + viewB.getItems().add(item); + } + } + + /** + * Returns the source list view (shown on the left-hand side). + * + * @return the source list view + */ + public final ListView<T> getSourceListView() { + return sourceListView; + } + + /** + * Returns the target list view (shown on the right-hand side). + * + * @return the target list view + */ + public final ListView<T> getTargetListView() { + return targetListView; + } + + /** + * Creates the {@link ListView} instance used on the left-hand side as the + * source list. This method can be overridden to provide a customized list + * view control. + * + * @return the source list view + */ + protected ListView<T> createSourceListView() { + return createListView(); + } + + /** + * Creates the {@link ListView} instance used on the right-hand side as the + * target list. This method can be overridden to provide a customized list + * view control. + * + * @return the target list view + */ + protected ListView<T> createTargetListView() { + return createListView(); + } + + private ListView<T> createListView() { + ListView<T> view = new ListView<>(); + view.getSelectionModel().setSelectionMode(MULTIPLE); + return view; + } +} diff --git a/src/impl/org/controlsfx/skin/MaskerPaneSkin.java b/src/impl/org/controlsfx/skin/MaskerPaneSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..0e7cf616cf6fabaf8330826cd1e7a9720088ae19 --- /dev/null +++ b/src/impl/org/controlsfx/skin/MaskerPaneSkin.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.controlsfx.control.MaskerPane; + +public class MaskerPaneSkin extends SkinBase<MaskerPane> { + + public MaskerPaneSkin(MaskerPane maskerPane) { + super(maskerPane); + getChildren().add(createMasker(maskerPane)); + } + + private StackPane createMasker(MaskerPane maskerPane) { + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER); + vBox.setSpacing(10.0); + vBox.getStyleClass().add("masker-center"); //$NON-NLS-1$ + + vBox.getChildren().add(createLabel()); + vBox.getChildren().add(createProgressIndicator()); + + HBox hBox = new HBox(); + hBox.setAlignment(Pos.CENTER); + hBox.getChildren().addAll(vBox); + + StackPane glass = new StackPane(); + glass.setAlignment(Pos.CENTER); + glass.getStyleClass().add("masker-glass"); //$NON-NLS-1$ + glass.getChildren().add(hBox); + + return glass; + } + + private Label createLabel() { + Label text = new Label(); + text.textProperty().bind(getSkinnable().textProperty()); + text.getStyleClass().add("masker-text"); //$NON-NLS-1$ + return text; + } + + private Label createProgressIndicator() { + Label graphic = new Label(); + graphic.setGraphic(getSkinnable().getProgressNode()); + graphic.visibleProperty().bind(getSkinnable().progressVisibleProperty()); + graphic.getStyleClass().add("masker-graphic"); //$NON-NLS-1$ + return graphic; + } +} diff --git a/src/impl/org/controlsfx/skin/MasterDetailPaneSkin.java b/src/impl/org/controlsfx/skin/MasterDetailPaneSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..8a954b89eac3ca713c98b422842994177de4b11a --- /dev/null +++ b/src/impl/org/controlsfx/skin/MasterDetailPaneSkin.java @@ -0,0 +1,473 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import static java.lang.Double.MAX_VALUE; +import static javafx.geometry.Orientation.HORIZONTAL; +import static javafx.geometry.Orientation.VERTICAL; + +import java.util.List; + +import javafx.animation.Animation; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.geometry.Side; +import javafx.scene.Node; +import javafx.scene.control.SkinBase; +import javafx.scene.control.SplitPane; +import javafx.scene.control.SplitPane.Divider; +import javafx.scene.layout.Region; +import javafx.util.Duration; + +import org.controlsfx.control.MasterDetailPane; + +public class MasterDetailPaneSkin extends SkinBase<MasterDetailPane> { + + private boolean changing = false; + private SplitPane splitPane; + private final Timeline timeline = new Timeline(); + private BooleanProperty showDetailForTimeline = new SimpleBooleanProperty(); + + public MasterDetailPaneSkin(MasterDetailPane pane) { + super(pane); + + this.splitPane = new SplitPane(); + this.splitPane.setDividerPosition(0, pane.getDividerPosition()); + + /** + * We listen to the change of dividers (when adding or removing node), and then + * we listen to their position to update correctly the dividerPosition of + * the MasterDetailPane. + */ + this.splitPane.getDividers().addListener(new ListChangeListener<Divider>() { + + @Override + public void onChanged(ListChangeListener.Change<? extends Divider> change) { + while (change.next()) { + if (change.wasAdded()) { + change.getAddedSubList().get(0).positionProperty().addListener(updateDividerPositionListener); + } else if (change.wasRemoved()) { + change.getRemoved().get(0).positionProperty().removeListener(updateDividerPositionListener); + } + } + } + }); + + SplitPane.setResizableWithParent(getSkinnable().getDetailNode(), false); + + switch (getSkinnable().getDetailSide()) { + case BOTTOM: + case TOP: + splitPane.setOrientation(VERTICAL); + break; + case LEFT: + case RIGHT: + splitPane.setOrientation(HORIZONTAL); + break; + } + + getSkinnable().masterNodeProperty().addListener( + new ChangeListener<Node>() { + @Override + public void changed(ObservableValue<? extends Node> value, + Node oldNode, Node newNode) { + + if (oldNode != null) { + splitPane.getItems().remove(oldNode); + } + + if (newNode != null) { + + updateMinAndMaxSizes(); + + int masterIndex = 0; + switch (splitPane.getOrientation()) { + case HORIZONTAL: + switch (getSkinnable().getDetailSide()) { + case LEFT: + masterIndex = 1; + break; + case RIGHT: + masterIndex = 0; + break; + default: + throw new IllegalArgumentException( + "illegal details position " //$NON-NLS-1$ + + getSkinnable() + .getDetailSide() + + " for orientation " //$NON-NLS-1$ + + splitPane + .getOrientation()); + } + break; + case VERTICAL: + switch (getSkinnable().getDetailSide()) { + case TOP: + masterIndex = 1; + break; + case BOTTOM: + masterIndex = 0; + break; + default: + throw new IllegalArgumentException( + "illegal details position " //$NON-NLS-1$ + + getSkinnable() + .getDetailSide() + + " for orientation " //$NON-NLS-1$ + + splitPane + .getOrientation()); + } + break; + } + List<Node> items = splitPane.getItems(); + if (items.isEmpty()) { + items.add(newNode); + } else { + items.add(masterIndex, newNode); + } + } + } + }); + + getSkinnable().detailNodeProperty().addListener( + new ChangeListener<Node>() { + @Override + public void changed(ObservableValue<? extends Node> value, + Node oldNode, Node newNode) { + + if (oldNode != null) { + splitPane.getItems().remove(oldNode); + } + + /** + * If the detailNode is not showing, we do not force + * it to show. + */ + if (newNode != null && getSkinnable().isShowDetailNode()) { + + /** + * Force the divider to take the value of the Pane, + * and not compute his. + */ + splitPane.setDividerPositions(getSkinnable().getDividerPosition()); + updateMinAndMaxSizes(); + + SplitPane.setResizableWithParent(newNode, false); + + int detailsIndex = 0; + switch (splitPane.getOrientation()) { + case HORIZONTAL: + switch (getSkinnable().getDetailSide()) { + case LEFT: + detailsIndex = 0; + break; + case RIGHT: + detailsIndex = 1; + break; + default: + throw new IllegalArgumentException( + "illegal details position " //$NON-NLS-1$ + + getSkinnable() + .getDetailSide() + + " for orientation " //$NON-NLS-1$ + + splitPane + .getOrientation()); + } + break; + case VERTICAL: + switch (getSkinnable().getDetailSide()) { + case TOP: + detailsIndex = 0; + break; + case BOTTOM: + detailsIndex = 1; + break; + default: + throw new IllegalArgumentException( + "illegal details position " //$NON-NLS-1$ + + getSkinnable() + .getDetailSide() + + " for orientation " //$NON-NLS-1$ + + splitPane + .getOrientation()); + } + break; + } + List<Node> items = splitPane.getItems(); + if (items.isEmpty()) { + items.add(newNode); + } else { + items.add(detailsIndex, newNode); + } + } + } + }); + + getSkinnable().showDetailNodeProperty().addListener( + new ChangeListener<Boolean>() { + @Override + public void changed( + ObservableValue<? extends Boolean> value, + Boolean oldShow, Boolean newShow) { + /** + * https://bitbucket.org/controlsfx/controlsfx/issue/456/masterdetailpane-bug-of-adding-infinite + * + * Fixed bug - when close or show is still animated jump to last frame of animation + ** and fire finished event to complete the previous demand + * + */ + if (getSkinnable().isAnimated() && timeline.getStatus() == Animation.Status.RUNNING) { + timeline.jumpTo("endAnimation"); + timeline.getOnFinished().handle(null); + } + + if (newShow) { + open(); + } else { + close(); + } + } + }); + + getSkinnable().detailSideProperty().addListener( + new ChangeListener<Side>() { + @Override + public void changed(ObservableValue<? extends Side> value, + Side oldPos, Side newPos) { + if (getSkinnable().isShowDetailNode()) { + splitPane.getItems().clear(); + } + switch (newPos) { + case TOP: + case BOTTOM: + splitPane.setOrientation(VERTICAL); + break; + case LEFT: + case RIGHT: + splitPane.setOrientation(HORIZONTAL); + } + switch (newPos) { + case TOP: + case LEFT: + if (getSkinnable().isShowDetailNode()) { + splitPane.getItems().add( + getSkinnable().getDetailNode()); + splitPane.getItems().add( + getSkinnable().getMasterNode()); + } + switch (oldPos) { + case BOTTOM: + case RIGHT: + getSkinnable().setDividerPosition(1 - getSkinnable().getDividerPosition()); + break; + default: + break; + } + break; + case BOTTOM: + case RIGHT: + if (getSkinnable().isShowDetailNode()) { + splitPane.getItems().add( + getSkinnable().getMasterNode()); + splitPane.getItems().add( + getSkinnable().getDetailNode()); + } + switch (oldPos) { + case TOP: + case LEFT: + getSkinnable().setDividerPosition(1 - getSkinnable().getDividerPosition()); + break; + default: + break; + } + break; + } + if (getSkinnable().isShowDetailNode()) { + splitPane.setDividerPositions(getSkinnable().getDividerPosition()); + } + } + }); + + updateMinAndMaxSizes(); + + getChildren().add(splitPane); + + splitPane.getItems().add(getSkinnable().getMasterNode()); + + if (getSkinnable().isShowDetailNode()) { + switch (getSkinnable().getDetailSide()) { + case TOP: + case LEFT: + splitPane.getItems().add(0, getSkinnable().getDetailNode()); + break; + case BOTTOM: + case RIGHT: + splitPane.getItems().add(getSkinnable().getDetailNode()); + break; + } + + bindDividerPosition(); + } + + timeline.setOnFinished(evt -> { + if (!showDetailForTimeline.get()) { + unbindDividerPosition(); + splitPane.getItems().remove( + getSkinnable().getDetailNode()); + getSkinnable().getDetailNode().setOpacity(1); + } + changing = false; + }); + } + + private InvalidationListener listenersDivider = new InvalidationListener() { + @Override + public void invalidated(Observable arg0) { + changing = true; + splitPane.setDividerPosition(0, getSkinnable().getDividerPosition()); + changing = false; + } + }; + + private void bindDividerPosition() { + getSkinnable().dividerPositionProperty().addListener(listenersDivider); + } + + private void unbindDividerPosition() { + getSkinnable().dividerPositionProperty().removeListener(listenersDivider); + } + + private void updateMinAndMaxSizes() { + if (getSkinnable().getMasterNode() instanceof Region) { + ((Region) getSkinnable().getMasterNode()).setMinSize(0, 0); + ((Region) getSkinnable().getMasterNode()).setMaxSize(MAX_VALUE, + MAX_VALUE); + } + + if (getSkinnable().getDetailNode() instanceof Region) { + ((Region) getSkinnable().getDetailNode()).setMinSize(0, 0); + ((Region) getSkinnable().getDetailNode()).setMaxSize(MAX_VALUE, + MAX_VALUE); + } + } + + private void open() { + changing = true; + Node node = getSkinnable().getDetailNode(); + + switch (getSkinnable().getDetailSide()) { + case TOP: + case LEFT: + splitPane.getItems().add(0, node); + splitPane.setDividerPositions(0); + break; + case BOTTOM: + case RIGHT: + splitPane.getItems().add(node); + splitPane.setDividerPositions(1); + break; + } + + updateMinAndMaxSizes(); + maybeAnimatePositionChange(getSkinnable().getDividerPosition(), true); + } + + private void close() { + changing = true; + if (!splitPane.getDividers().isEmpty()) { + + /* + * Do we collapse by moving the divider to the left/right or + * top/bottom? + */ + double targetLocation = 0; + switch (getSkinnable().getDetailSide()) { + case BOTTOM: + case RIGHT: + targetLocation = 1; + break; + default: + break; + } + + maybeAnimatePositionChange(targetLocation, false); + } + } + + private void maybeAnimatePositionChange(final double position, + final boolean showDetail) { + showDetailForTimeline.set(showDetail); + + Divider divider = splitPane.getDividers().get(0); + + if (showDetailForTimeline.get()) { + unbindDividerPosition(); + bindDividerPosition(); + } + + if (getSkinnable().isAnimated()) { + KeyValue positionKeyValue = new KeyValue( + divider.positionProperty(), position); + KeyValue opacityKeyValue = new KeyValue(getSkinnable() + .getDetailNode().opacityProperty(), showDetailForTimeline.get() ? 1 : 0); + + KeyFrame keyFrame = new KeyFrame(Duration.seconds(.1), "endAnimation", positionKeyValue, opacityKeyValue); + + timeline.getKeyFrames().clear(); + timeline.getKeyFrames().add(keyFrame); + + timeline.playFromStart(); + } else { + getSkinnable().getDetailNode().setOpacity(1); + divider.setPosition(position); + + if (!showDetailForTimeline.get()) { + unbindDividerPosition(); + splitPane.getItems().remove(getSkinnable().getDetailNode()); + } + changing = false; + } + } + + private ChangeListener<Number> updateDividerPositionListener = new ChangeListener<Number>() { + + @Override + public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) { + if (!changing) { + getSkinnable().setDividerPosition(t1.doubleValue()); + } + } + }; +} diff --git a/src/impl/org/controlsfx/skin/NotificationBar.java b/src/impl/org/controlsfx/skin/NotificationBar.java new file mode 100644 index 0000000000000000000000000000000000000000..2702eba6ef37de72364f3cb3445719a3f9fdede3 --- /dev/null +++ b/src/impl/org/controlsfx/skin/NotificationBar.java @@ -0,0 +1,309 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.animation.Animation.Status; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.Label; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; + +import org.controlsfx.control.NotificationPane; +import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionUtils; + +@SuppressWarnings("deprecation") +public abstract class NotificationBar extends Region { + + private static final double MIN_HEIGHT = 40; + + final Label label; + Label title; + ButtonBar actionsBar; + Button closeBtn; + + private final GridPane pane; + + public DoubleProperty transition = new SimpleDoubleProperty() { + @Override protected void invalidated() { + requestContainerLayout(); + } + }; + + + public void requestContainerLayout() { + layoutChildren(); + } + + public String getTitle() { + return ""; //$NON-NLS-1$ + } + + public boolean isCloseButtonVisible() { + return true; + } + + public abstract String getText(); + public abstract Node getGraphic(); + public abstract ObservableList<Action> getActions(); + public abstract void hide(); + public abstract boolean isShowing(); + public abstract boolean isShowFromTop(); + + public abstract double getContainerHeight(); + public abstract void relocateInParent(double x, double y); + + public NotificationBar() { + getStyleClass().add("notification-bar"); //$NON-NLS-1$ + + setVisible(isShowing()); + + pane = new GridPane(); + pane.getStyleClass().add("pane"); //$NON-NLS-1$ + pane.setAlignment(Pos.BASELINE_LEFT); + getChildren().setAll(pane); + + // initialise title area, if one is set + String titleStr = getTitle(); + if (titleStr != null && ! titleStr.isEmpty()) { + title = new Label(); + title.getStyleClass().add("title"); //$NON-NLS-1$ + title.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + GridPane.setHgrow(title, Priority.ALWAYS); + + title.setText(titleStr); + title.opacityProperty().bind(transition); + } + + // initialise label area + label = new Label(); + label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + GridPane.setVgrow(label, Priority.ALWAYS); + GridPane.setHgrow(label, Priority.ALWAYS); + + label.setText(getText()); + label.setGraphic(getGraphic()); + label.opacityProperty().bind(transition); + + // initialise actions area + getActions().addListener(new InvalidationListener() { + @Override public void invalidated(Observable arg0) { + updatePane(); + } + }); + + // initialise close button area + closeBtn = new Button(); + closeBtn.setOnAction(new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent arg0) { + hide(); + } + }); + closeBtn.getStyleClass().setAll("close-button"); //$NON-NLS-1$ + StackPane graphic = new StackPane(); + graphic.getStyleClass().setAll("graphic"); //$NON-NLS-1$ + closeBtn.setGraphic(graphic); + closeBtn.setMinSize(17, 17); + closeBtn.setPrefSize(17, 17); + closeBtn.setFocusTraversable(false); + closeBtn.opacityProperty().bind(transition); + GridPane.setMargin(closeBtn, new Insets(0, 0, 0, 8)); + + // position the close button in the best place, depending on the height + double minHeight = minHeight(-1); + GridPane.setValignment(closeBtn, minHeight == MIN_HEIGHT ? VPos.CENTER : VPos.TOP); + + // put it all together + updatePane(); + } + + void updatePane() { + actionsBar = ActionUtils.createButtonBar(getActions()); + actionsBar.opacityProperty().bind(transition); + GridPane.setHgrow(actionsBar, Priority.SOMETIMES); + pane.getChildren().clear(); + + int row = 0; + + if (title != null) { + pane.add(title, 0, row++); + } + + pane.add(label, 0, row); + pane.add(actionsBar, 1, row); + + if (isCloseButtonVisible()) { + pane.add(closeBtn, 2, 0, 1, row+1); + } + } + + @Override protected void layoutChildren() { + final double w = getWidth(); + final double h = computePrefHeight(-1); + + final double notificationBarHeight = prefHeight(w); + final double notificationMinHeight = minHeight(w); + + if (isShowFromTop()) { + // place at top of area + pane.resize(w, h); + relocateInParent(0, (transition.get() - 1) * notificationMinHeight); + } else { + // place at bottom of area + pane.resize(w, notificationBarHeight); + relocateInParent(0, getContainerHeight() - notificationBarHeight); + } + } + + @Override protected double computeMinHeight(double width) { + return Math.max(super.computePrefHeight(width), MIN_HEIGHT); + } + + @Override protected double computePrefHeight(double width) { + return Math.max(pane.prefHeight(width), minHeight(width)) * transition.get(); + } + + public void doShow() { + transitionStartValue = 0; + doAnimationTransition(); + } + + public void doHide() { + transitionStartValue = 1; + doAnimationTransition(); + } + + + + // --- animation timeline code + private final Duration TRANSITION_DURATION = new Duration(350.0); + private Timeline timeline; + private double transitionStartValue; + private void doAnimationTransition() { + Duration duration; + + if (timeline != null && (timeline.getStatus() != Status.STOPPED)) { + duration = timeline.getCurrentTime(); + + // fix for #70 - the notification pane freezes up as it has zero + // duration to expand / contract + duration = duration == Duration.ZERO ? TRANSITION_DURATION : duration; + transitionStartValue = transition.get(); + // --- end of fix + + timeline.stop(); + } else { + duration = TRANSITION_DURATION; + } + + timeline = new Timeline(); + timeline.setCycleCount(1); + + KeyFrame k1, k2; + + if (isShowing()) { + k1 = new KeyFrame( + Duration.ZERO, + new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent event) { + // start expand + setCache(true); + setVisible(true); + + pane.fireEvent(new Event(NotificationPane.ON_SHOWING)); + } + }, + new KeyValue(transition, transitionStartValue) + ); + + k2 = new KeyFrame( + duration, + new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent event) { + // end expand + pane.setCache(false); + + pane.fireEvent(new Event(NotificationPane.ON_SHOWN)); + } + }, + new KeyValue(transition, 1, Interpolator.EASE_OUT) + + ); + } else { + k1 = new KeyFrame( + Duration.ZERO, + new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent event) { + // Start collapse + pane.setCache(true); + + pane.fireEvent(new Event(NotificationPane.ON_HIDING)); + } + }, + new KeyValue(transition, transitionStartValue) + ); + + k2 = new KeyFrame( + duration, + new EventHandler<ActionEvent>() { + @Override public void handle(ActionEvent event) { + // end collapse + setCache(false); + setVisible(false); + + pane.fireEvent(new Event(NotificationPane.ON_HIDDEN)); + } + }, + new KeyValue(transition, 0, Interpolator.EASE_IN) + ); + } + + timeline.getKeyFrames().setAll(k1, k2); + timeline.play(); + } +} + diff --git a/src/impl/org/controlsfx/skin/NotificationPaneSkin.java b/src/impl/org/controlsfx/skin/NotificationPaneSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..58d404608ff3580eb0a3c1d37e65dc91bfcdf042 --- /dev/null +++ b/src/impl/org/controlsfx/skin/NotificationPaneSkin.java @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import com.sun.javafx.scene.traversal.ParentTraversalEngine; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.shape.Rectangle; + +import org.controlsfx.control.NotificationPane; +import org.controlsfx.control.action.Action; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +public class NotificationPaneSkin extends BehaviorSkinBase<NotificationPane, BehaviorBase<NotificationPane>> { + + private NotificationBar notificationBar; + private Node content; + private Rectangle clip = new Rectangle(); + + public NotificationPaneSkin(final NotificationPane control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + notificationBar = new NotificationBar() { + @Override public void requestContainerLayout() { + control.requestLayout(); + } + + @Override public String getText() { + return control.getText(); + } + + @Override public Node getGraphic() { + return control.getGraphic(); + } + + @Override public ObservableList<Action> getActions() { + return control.getActions(); + } + + @Override public boolean isShowing() { + return control.isShowing(); + } + + @Override public boolean isShowFromTop() { + return control.isShowFromTop(); + } + + @Override public void hide() { + control.hide(); + } + + @Override public boolean isCloseButtonVisible() { + return control.isCloseButtonVisible(); + } + + @Override public double getContainerHeight() { + return control.getHeight(); + } + + @Override public void relocateInParent(double x, double y) { + notificationBar.relocate(x, y); + } + }; + + control.setClip(clip); + updateContent(); + + registerChangeListener(control.heightProperty(), "HEIGHT"); //$NON-NLS-1$ + registerChangeListener(control.contentProperty(), "CONTENT"); //$NON-NLS-1$ + registerChangeListener(control.textProperty(), "TEXT"); //$NON-NLS-1$ + registerChangeListener(control.graphicProperty(), "GRAPHIC"); //$NON-NLS-1$ + registerChangeListener(control.showingProperty(), "SHOWING"); //$NON-NLS-1$ + registerChangeListener(control.showFromTopProperty(), "SHOW_FROM_TOP"); //$NON-NLS-1$ + registerChangeListener(control.closeButtonVisibleProperty(), "CLOSE_BUTTON_VISIBLE"); //$NON-NLS-1$ + + // Fix for Issue #522: Prevent NotificationPane from receiving focus + ParentTraversalEngine engine = new ParentTraversalEngine(getSkinnable()); + getSkinnable().setImpl_traversalEngine(engine); + engine.setOverriddenFocusTraversability(false); + } + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if ("CONTENT".equals(p)) { //$NON-NLS-1$ + updateContent(); + } else if ("TEXT".equals(p)) { //$NON-NLS-1$ + notificationBar.label.setText(getSkinnable().getText()); + } else if ("GRAPHIC".equals(p)) { //$NON-NLS-1$ + notificationBar.label.setGraphic(getSkinnable().getGraphic()); + } else if ("SHOWING".equals(p)) { //$NON-NLS-1$ + if (getSkinnable().isShowing()) { + notificationBar.doShow(); + } else { + notificationBar.doHide(); + } + } else if ("SHOW_FROM_TOP".equals(p)) { //$NON-NLS-1$ + if (getSkinnable().isShowing()) { + getSkinnable().requestLayout(); + } + } else if ("CLOSE_BUTTON_VISIBLE".equals(p)) { //$NON-NLS-1$ + notificationBar.updatePane(); + }else if ( "HEIGHT".equals(p)){ + // For resolving https://bitbucket.org/controlsfx/controlsfx/issue/409 + if (getSkinnable().isShowing() && !getSkinnable().isShowFromTop()) { + notificationBar.requestLayout(); + } + } + } + + private void updateContent() { + if (content != null) { + getChildren().remove(content); + } + + content = getSkinnable().getContent(); + + if (content == null) { + getChildren().setAll(notificationBar); + } else { + getChildren().setAll(content, notificationBar); + } + } + + @Override protected void layoutChildren(double x, double y, double w, double h) { + final double notificationBarHeight = notificationBar.prefHeight(w); + + notificationBar.resize(w, notificationBarHeight); + + // layout the content + if (content != null) { + content.resizeRelocate(x, y, w, h); + } + + // and update the clip so that the notification bar does not draw outside + // the bounds of the notification pane + clip.setX(x); + clip.setY(y); + clip.setWidth(w); + clip.setHeight(h); + } + + @Override + protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return content == null ? 0 : content.minWidth(height); + }; + + @Override + protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return content == null ? 0 : content.minHeight(width); + }; + + @Override + protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return content == null ? 0 : content.prefWidth(height); + }; + + @Override + protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return content == null ? 0 : content.prefHeight(width); + }; + + @Override + protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return content == null ? 0 : content.maxWidth(height); + }; + + @Override + protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return content == null ? 0 : content.maxHeight(width); + }; +} diff --git a/src/impl/org/controlsfx/skin/PlusMinusSliderSkin.java b/src/impl/org/controlsfx/skin/PlusMinusSliderSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..f5bf821efabf800b7f275f4abe2430ff7e00b516 --- /dev/null +++ b/src/impl/org/controlsfx/skin/PlusMinusSliderSkin.java @@ -0,0 +1,160 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import static javafx.scene.input.MouseEvent.MOUSE_PRESSED; +import static javafx.scene.input.MouseEvent.MOUSE_RELEASED; +import javafx.animation.AnimationTimer; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.Orientation; +import javafx.scene.control.SkinBase; +import javafx.scene.control.Slider; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Region; +import javafx.util.Duration; + +import org.controlsfx.control.PlusMinusSlider; +import org.controlsfx.control.PlusMinusSlider.PlusMinusEvent; + +public class PlusMinusSliderSkin extends SkinBase<PlusMinusSlider> { + + private SliderReader reader; + + private Slider slider; + + private Region plusRegion; + + private Region minusRegion; + + private BorderPane borderPane; + + public PlusMinusSliderSkin(PlusMinusSlider adjuster) { + super(adjuster); + + /* + * We are not supporting any key events, yet. Adding this filter makes + * sure the user doesn't use the standard key bindings of the slider. In + * that case the thumb would not move itself back automatically (e.g. + * after pressing "arrow right"). + */ + adjuster.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent event) { + event.consume(); + } + }); + + slider = new Slider(-1, 1, 0); + + slider.valueProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> observable, + Number oldValue, Number newValue) { + getSkinnable().getProperties().put("plusminusslidervalue", //$NON-NLS-1$ + newValue.doubleValue()); + } + }); + + slider.orientationProperty().bind(adjuster.orientationProperty()); + + slider.addEventHandler(MOUSE_PRESSED, new EventHandler<MouseEvent>() { + + @Override + public void handle(MouseEvent evt) { + reader = new SliderReader(); + reader.start(); + } + }); + + slider.addEventHandler(MOUSE_RELEASED, new EventHandler<MouseEvent>() { + + @Override + public void handle(MouseEvent evt) { + if (reader != null) { + reader.stop(); + } + + KeyValue keyValue = new KeyValue(slider.valueProperty(), 0); + KeyFrame keyFrame = new KeyFrame(Duration.millis(100), keyValue); + Timeline timeline = new Timeline(keyFrame); + timeline.play(); + } + }); + + plusRegion = new Region(); + plusRegion.getStyleClass().add("adjust-plus"); //$NON-NLS-1$ + + minusRegion = new Region(); + minusRegion.getStyleClass().add("adjust-minus"); //$NON-NLS-1$ + + borderPane = new BorderPane(); + + updateLayout(adjuster.getOrientation()); + + getChildren().add(borderPane); + + adjuster.orientationProperty().addListener((observable, oldValue, newValue) -> updateLayout(newValue)); + } + + private void updateLayout(Orientation orientation) { + borderPane.getChildren().clear(); + + switch (orientation) { + case HORIZONTAL: + borderPane.setLeft(minusRegion); + borderPane.setCenter(slider); + borderPane.setRight(plusRegion); + break; + case VERTICAL: + borderPane.setTop(plusRegion); + borderPane.setCenter(slider); + borderPane.setBottom(minusRegion); + break; + } + } + + class SliderReader extends AnimationTimer { + private long lastTime = System.currentTimeMillis(); + + @Override + public void handle(long now) { + // max speed: 100 hundred times per second + if (now - lastTime > 10000000) { + lastTime = now; + slider.fireEvent(new PlusMinusEvent(slider, slider, + PlusMinusEvent.VALUE_CHANGED, slider.getValue())); + } + } + } +} diff --git a/src/impl/org/controlsfx/skin/PopOverSkin.java b/src/impl/org/controlsfx/skin/PopOverSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..0d18a86c6002cf852990c6996b17a829391efea2 --- /dev/null +++ b/src/impl/org/controlsfx/skin/PopOverSkin.java @@ -0,0 +1,717 @@ +/** + * Copyright (c) 2013 - 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import static java.lang.Double.MAX_VALUE; +import static javafx.geometry.Pos.CENTER_LEFT; +import static javafx.scene.control.ContentDisplay.GRAPHIC_ONLY; +import static javafx.scene.paint.Color.YELLOW; +import static org.controlsfx.control.PopOver.ArrowLocation.BOTTOM_CENTER; +import static org.controlsfx.control.PopOver.ArrowLocation.BOTTOM_LEFT; +import static org.controlsfx.control.PopOver.ArrowLocation.BOTTOM_RIGHT; +import static org.controlsfx.control.PopOver.ArrowLocation.LEFT_BOTTOM; +import static org.controlsfx.control.PopOver.ArrowLocation.LEFT_CENTER; +import static org.controlsfx.control.PopOver.ArrowLocation.LEFT_TOP; +import static org.controlsfx.control.PopOver.ArrowLocation.RIGHT_BOTTOM; +import static org.controlsfx.control.PopOver.ArrowLocation.RIGHT_CENTER; +import static org.controlsfx.control.PopOver.ArrowLocation.RIGHT_TOP; +import static org.controlsfx.control.PopOver.ArrowLocation.TOP_CENTER; +import static org.controlsfx.control.PopOver.ArrowLocation.TOP_LEFT; +import static org.controlsfx.control.PopOver.ArrowLocation.TOP_RIGHT; + +import java.util.ArrayList; +import java.util.List; + +import org.controlsfx.control.PopOver; +import org.controlsfx.control.PopOver.ArrowLocation; + +import javafx.beans.InvalidationListener; +import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.event.EventHandler; +import javafx.geometry.Point2D; +import javafx.geometry.Pos; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Skin; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Circle; +import javafx.scene.shape.HLineTo; +import javafx.scene.shape.Line; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.scene.shape.QuadCurveTo; +import javafx.scene.shape.VLineTo; +import javafx.stage.Window; + +public class PopOverSkin implements Skin<PopOver> { + + private static final String DETACHED_STYLE_CLASS = "detached"; //$NON-NLS-1$ + + private double xOffset; + private double yOffset; + + private boolean tornOff; + + private Label title; + private Label closeIcon; + + private Path path; + private Path clip; + + private BorderPane content; + private StackPane titlePane; + private StackPane stackPane; + + private Point2D dragStartLocation; + + private PopOver popOver; + + public PopOverSkin(final PopOver popOver) { + + this.popOver = popOver; + + stackPane = popOver.getRoot(); + stackPane.setPickOnBounds(false); + + Bindings.bindContent(stackPane.getStyleClass(), popOver.getStyleClass()); + + /* + * The min width and height equal 2 * corner radius + 2 * arrow indent + + * 2 * arrow size. + */ + stackPane.minWidthProperty().bind( + Bindings.add(Bindings.multiply(2, popOver.arrowSizeProperty()), + Bindings.add( + Bindings.multiply(2, + popOver.cornerRadiusProperty()), + Bindings.multiply(2, + popOver.arrowIndentProperty())))); + + stackPane.minHeightProperty().bind(stackPane.minWidthProperty()); + + title = new Label(); + title.textProperty().bind(popOver.titleProperty()); + title.setMaxSize(MAX_VALUE, MAX_VALUE); + title.setAlignment(Pos.CENTER); + title.getStyleClass().add("text"); //$NON-NLS-1$ + + closeIcon = new Label(); + closeIcon.setGraphic(createCloseIcon()); + closeIcon.setMaxSize(MAX_VALUE, MAX_VALUE); + closeIcon.setContentDisplay(GRAPHIC_ONLY); + closeIcon.visibleProperty().bind(popOver.detachedProperty().or(popOver.headerAlwaysVisibleProperty())); + closeIcon.getStyleClass().add("icon"); //$NON-NLS-1$ + closeIcon.setAlignment(CENTER_LEFT); + closeIcon.getGraphic().setOnMouseClicked(evt -> popOver.hide()); + + titlePane = new StackPane(); + titlePane.getChildren().add(title); + titlePane.getChildren().add(closeIcon); + titlePane.getStyleClass().add("title"); //$NON-NLS-1$ + + content = new BorderPane(); + content.setCenter(popOver.getContentNode()); + content.getStyleClass().add("content"); //$NON-NLS-1$ + + if (popOver.isDetached() || popOver.isHeaderAlwaysVisible()) { + content.setTop(titlePane); + } + + if (popOver.isDetached()) { + popOver.getStyleClass().add(DETACHED_STYLE_CLASS); + content.getStyleClass().add(DETACHED_STYLE_CLASS); + } + + popOver.headerAlwaysVisibleProperty().addListener((o, oV, isVisible) -> { + if (isVisible) { + content.setTop(titlePane); + } else if (!popOver.isDetached()) { + content.setTop(null); + } + }); + + InvalidationListener updatePathListener = observable -> updatePath(); + getPopupWindow().xProperty().addListener(updatePathListener); + getPopupWindow().yProperty().addListener(updatePathListener); + popOver.arrowLocationProperty().addListener(updatePathListener); + popOver.contentNodeProperty().addListener( + (value, oldContent, newContent) -> content + .setCenter(newContent)); + popOver.detachedProperty() + .addListener((value, oldDetached, newDetached) -> { + + if (newDetached) { + popOver.getStyleClass().add(DETACHED_STYLE_CLASS); + content.getStyleClass().add(DETACHED_STYLE_CLASS); + content.setTop(titlePane); + + switch (getSkinnable().getArrowLocation()) { + case LEFT_TOP: + case LEFT_CENTER: + case LEFT_BOTTOM: + popOver.setAnchorX( + popOver.getAnchorX() + popOver.getArrowSize()); + break; + case TOP_LEFT: + case TOP_CENTER: + case TOP_RIGHT: + popOver.setAnchorY( + popOver.getAnchorY() + popOver.getArrowSize()); + break; + default: + break; + } + } else { + popOver.getStyleClass().remove(DETACHED_STYLE_CLASS); + content.getStyleClass().remove(DETACHED_STYLE_CLASS); + + if (!popOver.isHeaderAlwaysVisible()) { + content.setTop(null); + } + } + + popOver.sizeToScene(); + + updatePath(); + }); + + path = new Path(); + path.getStyleClass().add("border"); //$NON-NLS-1$ + path.setManaged(false); + + clip = new Path(); + + /* + * The clip is a path and the path has to be filled with a color. + * Otherwise clipping will not work. + */ + clip.setFill(YELLOW); + + createPathElements(); + updatePath(); + + final EventHandler<MouseEvent> mousePressedHandler = evt -> { + if (popOver.isDetachable() || popOver.isDetached()) { + tornOff = false; + + xOffset = evt.getScreenX(); + yOffset = evt.getScreenY(); + + dragStartLocation = new Point2D(xOffset, yOffset); + } + }; + + final EventHandler<MouseEvent> mouseReleasedHandler = evt -> { + if (tornOff && !getSkinnable().isDetached()) { + tornOff = false; + getSkinnable().detach(); + } + }; + + final EventHandler<MouseEvent> mouseDragHandler = evt -> { + if (popOver.isDetachable() || popOver.isDetached()) { + double deltaX = evt.getScreenX() - xOffset; + double deltaY = evt.getScreenY() - yOffset; + + Window window = getSkinnable().getScene().getWindow(); + + window.setX(window.getX() + deltaX); + window.setY(window.getY() + deltaY); + + xOffset = evt.getScreenX(); + yOffset = evt.getScreenY(); + + if (dragStartLocation.distance(xOffset, yOffset) > 20) { + tornOff = true; + updatePath(); + } else if (tornOff) { + tornOff = false; + updatePath(); + } + } + }; + + stackPane.setOnMousePressed(mousePressedHandler); + stackPane.setOnMouseDragged(mouseDragHandler); + stackPane.setOnMouseReleased(mouseReleasedHandler); + + stackPane.getChildren().add(path); + stackPane.getChildren().add(content); + + content.setClip(clip); + } + + @Override + public Node getNode() { + return stackPane; + } + + @Override + public PopOver getSkinnable() { + return popOver; + } + + @Override + public void dispose() { + } + + private Node createCloseIcon() { + Group group = new Group(); + group.getStyleClass().add("graphics"); //$NON-NLS-1$ + + Circle circle = new Circle(); + circle.getStyleClass().add("circle"); //$NON-NLS-1$ + circle.setRadius(6); + circle.setCenterX(6); + circle.setCenterY(6); + group.getChildren().add(circle); + + Line line1 = new Line(); + line1.getStyleClass().add("line"); //$NON-NLS-1$ + line1.setStartX(4); + line1.setStartY(4); + line1.setEndX(8); + line1.setEndY(8); + group.getChildren().add(line1); + + Line line2 = new Line(); + line2.getStyleClass().add("line"); //$NON-NLS-1$ + line2.setStartX(8); + line2.setStartY(4); + line2.setEndX(4); + line2.setEndY(8); + group.getChildren().add(line2); + + return group; + } + + private MoveTo moveTo; + + private QuadCurveTo topCurveTo, rightCurveTo, bottomCurveTo, leftCurveTo; + + private HLineTo lineBTop, lineETop, lineHTop, lineKTop; + private LineTo lineCTop, lineDTop, lineFTop, lineGTop, lineITop, lineJTop; + + private VLineTo lineBRight, lineERight, lineHRight, lineKRight; + private LineTo lineCRight, lineDRight, lineFRight, lineGRight, lineIRight, + lineJRight; + + private HLineTo lineBBottom, lineEBottom, lineHBottom, lineKBottom; + private LineTo lineCBottom, lineDBottom, lineFBottom, lineGBottom, + lineIBottom, lineJBottom; + + private VLineTo lineBLeft, lineELeft, lineHLeft, lineKLeft; + private LineTo lineCLeft, lineDLeft, lineFLeft, lineGLeft, lineILeft, + lineJLeft; + + private void createPathElements() { + DoubleProperty centerYProperty = new SimpleDoubleProperty(); + DoubleProperty centerXProperty = new SimpleDoubleProperty(); + + DoubleProperty leftEdgeProperty = new SimpleDoubleProperty(); + DoubleProperty leftEdgePlusRadiusProperty = new SimpleDoubleProperty(); + + DoubleProperty topEdgeProperty = new SimpleDoubleProperty(); + DoubleProperty topEdgePlusRadiusProperty = new SimpleDoubleProperty(); + + DoubleProperty rightEdgeProperty = new SimpleDoubleProperty(); + DoubleProperty rightEdgeMinusRadiusProperty = new SimpleDoubleProperty(); + + DoubleProperty bottomEdgeProperty = new SimpleDoubleProperty(); + DoubleProperty bottomEdgeMinusRadiusProperty = new SimpleDoubleProperty(); + + DoubleProperty cornerProperty = getSkinnable().cornerRadiusProperty(); + + DoubleProperty arrowSizeProperty = getSkinnable().arrowSizeProperty(); + DoubleProperty arrowIndentProperty = getSkinnable() + .arrowIndentProperty(); + + centerYProperty.bind(Bindings.divide(stackPane.heightProperty(), 2)); + centerXProperty.bind(Bindings.divide(stackPane.widthProperty(), 2)); + + leftEdgePlusRadiusProperty.bind(Bindings.add(leftEdgeProperty, + getSkinnable().cornerRadiusProperty())); + + topEdgePlusRadiusProperty.bind(Bindings.add(topEdgeProperty, + getSkinnable().cornerRadiusProperty())); + + rightEdgeProperty.bind(stackPane.widthProperty()); + rightEdgeMinusRadiusProperty.bind(Bindings.subtract(rightEdgeProperty, + getSkinnable().cornerRadiusProperty())); + + bottomEdgeProperty.bind(stackPane.heightProperty()); + bottomEdgeMinusRadiusProperty.bind(Bindings.subtract( + bottomEdgeProperty, getSkinnable().cornerRadiusProperty())); + + // INIT + moveTo = new MoveTo(); + moveTo.xProperty().bind(leftEdgePlusRadiusProperty); + moveTo.yProperty().bind(topEdgeProperty); + + // + // TOP EDGE + // + lineBTop = new HLineTo(); + lineBTop.xProperty().bind( + Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty)); + + lineCTop = new LineTo(); + lineCTop.xProperty().bind( + Bindings.add(lineBTop.xProperty(), arrowSizeProperty)); + lineCTop.yProperty().bind( + Bindings.subtract(topEdgeProperty, arrowSizeProperty)); + + lineDTop = new LineTo(); + lineDTop.xProperty().bind( + Bindings.add(lineCTop.xProperty(), arrowSizeProperty)); + lineDTop.yProperty().bind(topEdgeProperty); + + lineETop = new HLineTo(); + lineETop.xProperty().bind( + Bindings.subtract(centerXProperty, arrowSizeProperty)); + + lineFTop = new LineTo(); + lineFTop.xProperty().bind(centerXProperty); + lineFTop.yProperty().bind( + Bindings.subtract(topEdgeProperty, arrowSizeProperty)); + + lineGTop = new LineTo(); + lineGTop.xProperty().bind( + Bindings.add(centerXProperty, arrowSizeProperty)); + lineGTop.yProperty().bind(topEdgeProperty); + + lineHTop = new HLineTo(); + lineHTop.xProperty().bind( + Bindings.subtract(Bindings.subtract( + rightEdgeMinusRadiusProperty, arrowIndentProperty), + Bindings.multiply(arrowSizeProperty, 2))); + + lineITop = new LineTo(); + lineITop.xProperty().bind( + Bindings.subtract(Bindings.subtract( + rightEdgeMinusRadiusProperty, arrowIndentProperty), + arrowSizeProperty)); + lineITop.yProperty().bind( + Bindings.subtract(topEdgeProperty, arrowSizeProperty)); + + lineJTop = new LineTo(); + lineJTop.xProperty().bind( + Bindings.subtract(rightEdgeMinusRadiusProperty, + arrowIndentProperty)); + lineJTop.yProperty().bind(topEdgeProperty); + + lineKTop = new HLineTo(); + lineKTop.xProperty().bind(rightEdgeMinusRadiusProperty); + + // + // RIGHT EDGE + // + rightCurveTo = new QuadCurveTo(); + rightCurveTo.xProperty().bind(rightEdgeProperty); + rightCurveTo.yProperty().bind( + Bindings.add(topEdgeProperty, cornerProperty)); + rightCurveTo.controlXProperty().bind(rightEdgeProperty); + rightCurveTo.controlYProperty().bind(topEdgeProperty); + + lineBRight = new VLineTo(); + lineBRight.yProperty().bind( + Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty)); + + lineCRight = new LineTo(); + lineCRight.xProperty().bind( + Bindings.add(rightEdgeProperty, arrowSizeProperty)); + lineCRight.yProperty().bind( + Bindings.add(lineBRight.yProperty(), arrowSizeProperty)); + + lineDRight = new LineTo(); + lineDRight.xProperty().bind(rightEdgeProperty); + lineDRight.yProperty().bind( + Bindings.add(lineCRight.yProperty(), arrowSizeProperty)); + + lineERight = new VLineTo(); + lineERight.yProperty().bind( + Bindings.subtract(centerYProperty, arrowSizeProperty)); + + lineFRight = new LineTo(); + lineFRight.xProperty().bind( + Bindings.add(rightEdgeProperty, arrowSizeProperty)); + lineFRight.yProperty().bind(centerYProperty); + + lineGRight = new LineTo(); + lineGRight.xProperty().bind(rightEdgeProperty); + lineGRight.yProperty().bind( + Bindings.add(centerYProperty, arrowSizeProperty)); + + lineHRight = new VLineTo(); + lineHRight.yProperty().bind( + Bindings.subtract(Bindings.subtract( + bottomEdgeMinusRadiusProperty, arrowIndentProperty), + Bindings.multiply(arrowSizeProperty, 2))); + + lineIRight = new LineTo(); + lineIRight.xProperty().bind( + Bindings.add(rightEdgeProperty, arrowSizeProperty)); + lineIRight.yProperty().bind( + Bindings.subtract(Bindings.subtract( + bottomEdgeMinusRadiusProperty, arrowIndentProperty), + arrowSizeProperty)); + + lineJRight = new LineTo(); + lineJRight.xProperty().bind(rightEdgeProperty); + lineJRight.yProperty().bind( + Bindings.subtract(bottomEdgeMinusRadiusProperty, + arrowIndentProperty)); + + lineKRight = new VLineTo(); + lineKRight.yProperty().bind(bottomEdgeMinusRadiusProperty); + + // + // BOTTOM EDGE + // + + bottomCurveTo = new QuadCurveTo(); + bottomCurveTo.xProperty().bind(rightEdgeMinusRadiusProperty); + bottomCurveTo.yProperty().bind(bottomEdgeProperty); + bottomCurveTo.controlXProperty().bind(rightEdgeProperty); + bottomCurveTo.controlYProperty().bind(bottomEdgeProperty); + + lineBBottom = new HLineTo(); + lineBBottom.xProperty().bind( + Bindings.subtract(rightEdgeMinusRadiusProperty, + arrowIndentProperty)); + + lineCBottom = new LineTo(); + lineCBottom.xProperty().bind( + Bindings.subtract(lineBBottom.xProperty(), arrowSizeProperty)); + lineCBottom.yProperty().bind( + Bindings.add(bottomEdgeProperty, arrowSizeProperty)); + + lineDBottom = new LineTo(); + lineDBottom.xProperty().bind( + Bindings.subtract(lineCBottom.xProperty(), arrowSizeProperty)); + lineDBottom.yProperty().bind(bottomEdgeProperty); + + lineEBottom = new HLineTo(); + lineEBottom.xProperty().bind( + Bindings.add(centerXProperty, arrowSizeProperty)); + + lineFBottom = new LineTo(); + lineFBottom.xProperty().bind(centerXProperty); + lineFBottom.yProperty().bind( + Bindings.add(bottomEdgeProperty, arrowSizeProperty)); + + lineGBottom = new LineTo(); + lineGBottom.xProperty().bind( + Bindings.subtract(centerXProperty, arrowSizeProperty)); + lineGBottom.yProperty().bind(bottomEdgeProperty); + + lineHBottom = new HLineTo(); + lineHBottom.xProperty().bind( + Bindings.add(Bindings.add(leftEdgePlusRadiusProperty, + arrowIndentProperty), Bindings.multiply( + arrowSizeProperty, 2))); + + lineIBottom = new LineTo(); + lineIBottom.xProperty().bind( + Bindings.add(Bindings.add(leftEdgePlusRadiusProperty, + arrowIndentProperty), arrowSizeProperty)); + lineIBottom.yProperty().bind( + Bindings.add(bottomEdgeProperty, arrowSizeProperty)); + + lineJBottom = new LineTo(); + lineJBottom.xProperty().bind( + Bindings.add(leftEdgePlusRadiusProperty, arrowIndentProperty)); + lineJBottom.yProperty().bind(bottomEdgeProperty); + + lineKBottom = new HLineTo(); + lineKBottom.xProperty().bind(leftEdgePlusRadiusProperty); + + // + // LEFT EDGE + // + leftCurveTo = new QuadCurveTo(); + leftCurveTo.xProperty().bind(leftEdgeProperty); + leftCurveTo.yProperty().bind( + Bindings.subtract(bottomEdgeProperty, cornerProperty)); + leftCurveTo.controlXProperty().bind(leftEdgeProperty); + leftCurveTo.controlYProperty().bind(bottomEdgeProperty); + + lineBLeft = new VLineTo(); + lineBLeft.yProperty().bind( + Bindings.subtract(bottomEdgeMinusRadiusProperty, + arrowIndentProperty)); + + lineCLeft = new LineTo(); + lineCLeft.xProperty().bind( + Bindings.subtract(leftEdgeProperty, arrowSizeProperty)); + lineCLeft.yProperty().bind( + Bindings.subtract(lineBLeft.yProperty(), arrowSizeProperty)); + + lineDLeft = new LineTo(); + lineDLeft.xProperty().bind(leftEdgeProperty); + lineDLeft.yProperty().bind( + Bindings.subtract(lineCLeft.yProperty(), arrowSizeProperty)); + + lineELeft = new VLineTo(); + lineELeft.yProperty().bind( + Bindings.add(centerYProperty, arrowSizeProperty)); + + lineFLeft = new LineTo(); + lineFLeft.xProperty().bind( + Bindings.subtract(leftEdgeProperty, arrowSizeProperty)); + lineFLeft.yProperty().bind(centerYProperty); + + lineGLeft = new LineTo(); + lineGLeft.xProperty().bind(leftEdgeProperty); + lineGLeft.yProperty().bind( + Bindings.subtract(centerYProperty, arrowSizeProperty)); + + lineHLeft = new VLineTo(); + lineHLeft.yProperty().bind( + Bindings.add(Bindings.add(topEdgePlusRadiusProperty, + arrowIndentProperty), Bindings.multiply( + arrowSizeProperty, 2))); + + lineILeft = new LineTo(); + lineILeft.xProperty().bind( + Bindings.subtract(leftEdgeProperty, arrowSizeProperty)); + lineILeft.yProperty().bind( + Bindings.add(Bindings.add(topEdgePlusRadiusProperty, + arrowIndentProperty), arrowSizeProperty)); + + lineJLeft = new LineTo(); + lineJLeft.xProperty().bind(leftEdgeProperty); + lineJLeft.yProperty().bind( + Bindings.add(topEdgePlusRadiusProperty, arrowIndentProperty)); + + lineKLeft = new VLineTo(); + lineKLeft.yProperty().bind(topEdgePlusRadiusProperty); + + topCurveTo = new QuadCurveTo(); + topCurveTo.xProperty().bind(leftEdgePlusRadiusProperty); + topCurveTo.yProperty().bind(topEdgeProperty); + topCurveTo.controlXProperty().bind(leftEdgeProperty); + topCurveTo.controlYProperty().bind(topEdgeProperty); + } + + private Window getPopupWindow() { + return getSkinnable().getScene().getWindow(); + } + + private boolean showArrow(ArrowLocation location) { + ArrowLocation arrowLocation = getSkinnable().getArrowLocation(); + return location.equals(arrowLocation) && !getSkinnable().isDetached() + && !tornOff; + } + + private void updatePath() { + List<PathElement> elements = new ArrayList<>(); + elements.add(moveTo); + + if (showArrow(TOP_LEFT)) { + elements.add(lineBTop); + elements.add(lineCTop); + elements.add(lineDTop); + } + if (showArrow(TOP_CENTER)) { + elements.add(lineETop); + elements.add(lineFTop); + elements.add(lineGTop); + } + if (showArrow(TOP_RIGHT)) { + elements.add(lineHTop); + elements.add(lineITop); + elements.add(lineJTop); + } + elements.add(lineKTop); + elements.add(rightCurveTo); + + if (showArrow(RIGHT_TOP)) { + elements.add(lineBRight); + elements.add(lineCRight); + elements.add(lineDRight); + } + if (showArrow(RIGHT_CENTER)) { + elements.add(lineERight); + elements.add(lineFRight); + elements.add(lineGRight); + } + if (showArrow(RIGHT_BOTTOM)) { + elements.add(lineHRight); + elements.add(lineIRight); + elements.add(lineJRight); + } + elements.add(lineKRight); + elements.add(bottomCurveTo); + + if (showArrow(BOTTOM_RIGHT)) { + elements.add(lineBBottom); + elements.add(lineCBottom); + elements.add(lineDBottom); + } + if (showArrow(BOTTOM_CENTER)) { + elements.add(lineEBottom); + elements.add(lineFBottom); + elements.add(lineGBottom); + } + if (showArrow(BOTTOM_LEFT)) { + elements.add(lineHBottom); + elements.add(lineIBottom); + elements.add(lineJBottom); + } + elements.add(lineKBottom); + elements.add(leftCurveTo); + + if (showArrow(LEFT_BOTTOM)) { + elements.add(lineBLeft); + elements.add(lineCLeft); + elements.add(lineDLeft); + } + if (showArrow(LEFT_CENTER)) { + elements.add(lineELeft); + elements.add(lineFLeft); + elements.add(lineGLeft); + } + if (showArrow(LEFT_TOP)) { + elements.add(lineHLeft); + elements.add(lineILeft); + elements.add(lineJLeft); + } + elements.add(lineKLeft); + elements.add(topCurveTo); + + path.getElements().setAll(elements); + clip.getElements().setAll(elements); + } +} diff --git a/src/impl/org/controlsfx/skin/PropertySheetSkin.java b/src/impl/org/controlsfx/skin/PropertySheetSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..f050288b4bc85a4ec2d4b2f4ecc57fa15bd40970 --- /dev/null +++ b/src/impl/org/controlsfx/skin/PropertySheetSkin.java @@ -0,0 +1,350 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Accordion; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; + +import org.controlsfx.control.PropertySheet; +import org.controlsfx.control.PropertySheet.Item; +import org.controlsfx.control.PropertySheet.Mode; +import org.controlsfx.control.SegmentedButton; +import org.controlsfx.control.action.Action; +import org.controlsfx.control.action.ActionUtils; +import org.controlsfx.control.textfield.TextFields; +import org.controlsfx.property.editor.AbstractPropertyEditor; +import org.controlsfx.property.editor.PropertyEditor; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +public class PropertySheetSkin extends BehaviorSkinBase<PropertySheet, BehaviorBase<PropertySheet>> { + + /************************************************************************** + * + * Static fields + * + **************************************************************************/ + + private static final int MIN_COLUMN_WIDTH = 100; + + /************************************************************************** + * + * fields + * + **************************************************************************/ + + private final BorderPane content; + private final ScrollPane scroller; + private final ToolBar toolbar; + private final SegmentedButton modeButton = ActionUtils.createSegmentedButton( + new ActionChangeMode(Mode.NAME), + new ActionChangeMode(Mode.CATEGORY) + ); + private final TextField searchField = TextFields.createClearableTextField(); + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + public PropertySheetSkin(final PropertySheet control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + scroller = new ScrollPane(); + scroller.setFitToWidth(true); + + toolbar = new ToolBar(); + toolbar.managedProperty().bind(toolbar.visibleProperty()); + toolbar.setFocusTraversable(true); + + // property sheet mode + modeButton.managedProperty().bind(modeButton.visibleProperty()); + modeButton.getButtons().get(getSkinnable().modeProperty().get().ordinal()).setSelected(true); + toolbar.getItems().add(modeButton); + + // property sheet search + searchField.setPromptText( localize(asKey("property.sheet.search.field.prompt"))); //$NON-NLS-1$ + searchField.setMinWidth(0); + HBox.setHgrow(searchField, Priority.SOMETIMES); + searchField.managedProperty().bind(searchField.visibleProperty()); + toolbar.getItems().add(searchField); + + // layout controls + content = new BorderPane(); + content.setTop(toolbar); + content.setCenter(scroller); + getChildren().add(content); + + + // setup listeners + registerChangeListener(control.modeProperty(), "MODE"); //$NON-NLS-1$ + registerChangeListener(control.propertyEditorFactory(), "EDITOR-FACTORY"); //$NON-NLS-1$ + registerChangeListener(control.titleFilter(), "FILTER"); //$NON-NLS-1$ + registerChangeListener(searchField.textProperty(), "FILTER-UI"); //$NON-NLS-1$ + registerChangeListener(control.modeSwitcherVisibleProperty(), "TOOLBAR-MODE"); //$NON-NLS-1$ + registerChangeListener(control.searchBoxVisibleProperty(), "TOOLBAR-SEARCH"); //$NON-NLS-1$ + + control.getItems().addListener((ListChangeListener<Item>) change -> refreshProperties()); + + // initialize properly + refreshProperties(); + updateToolbar(); + } + + + /************************************************************************** + * + * Overriding public API + * + **************************************************************************/ + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if (p == "MODE" || p == "EDITOR-FACTORY" || p == "FILTER") { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + refreshProperties(); + } else if (p == "FILTER-UI") { //$NON-NLS-1$ + getSkinnable().setTitleFilter(searchField.getText()); + } else if (p == "TOOLBAR-MODE") { //$NON-NLS-1$ + updateToolbar(); + } else if (p == "TOOLBAR-SEARCH") { //$NON-NLS-1$ + updateToolbar(); + } + } + + @Override protected void layoutChildren(double x, double y, double w, double h) { + content.resizeRelocate(x, y, w, h); + } + + + + /************************************************************************** + * + * Implementation + * + **************************************************************************/ + + private void updateToolbar() { + modeButton.setVisible(getSkinnable().isModeSwitcherVisible()); + searchField.setVisible(getSkinnable().isSearchBoxVisible()); + + toolbar.setVisible(modeButton.isVisible() || searchField.isVisible()); + } + + private void refreshProperties() { + scroller.setContent(buildPropertySheetContainer()); + } + + private Node buildPropertySheetContainer() { + switch( getSkinnable().modeProperty().get() ) { + case CATEGORY: { + // group by category + Map<String, List<Item>> categoryMap = new TreeMap<>(); + for( Item p: getSkinnable().getItems()) { + String category = p.getCategory(); + List<Item> list = categoryMap.get(category); + if ( list == null ) { + list = new ArrayList<>(); + categoryMap.put( category, list); + } + list.add(p); + } + + // create category-based accordion + Accordion accordion = new Accordion(); + for( String category: categoryMap.keySet() ) { + PropertyPane props = new PropertyPane( categoryMap.get(category)); + // Only show non-empty categories + if ( props.getChildrenUnmodifiable().size() > 0 ) { + TitledPane pane = new TitledPane( category, props ); + pane.setExpanded(true); + accordion.getPanes().add(pane); + } + } + if ( accordion.getPanes().size() > 0 ) { + accordion.setExpandedPane(accordion.getPanes().get(0)); + } + return accordion; + } + + default: return new PropertyPane(getSkinnable().getItems()); + } + + } + + + /************************************************************************** + * + * Support classes / enums + * + **************************************************************************/ + + private class ActionChangeMode extends Action { + + private final Image CATEGORY_IMAGE = new Image(PropertySheetSkin.class.getResource("/org/controlsfx/control/format-indent-more.png").toExternalForm()); //$NON-NLS-1$ + private final Image NAME_IMAGE = new Image(PropertySheetSkin.class.getResource("/org/controlsfx/control/format-line-spacing-triple.png").toExternalForm()); //$NON-NLS-1$ + + public ActionChangeMode(PropertySheet.Mode mode) { + super(""); //$NON-NLS-1$ + setEventHandler(ae -> getSkinnable().modeProperty().set(mode)); + + if (mode == Mode.CATEGORY) { + setGraphic( new ImageView(CATEGORY_IMAGE)); + setLongText(localize(asKey("property.sheet.group.mode.bycategory"))); //$NON-NLS-1$ + } else if (mode == Mode.NAME) { + setGraphic(new ImageView(NAME_IMAGE)); + setLongText(localize(asKey("property.sheet.group.mode.byname"))); //$NON-NLS-1$ + } else { + setText("???"); //$NON-NLS-1$ + } + } + + } + + + private class PropertyPane extends GridPane { + + public PropertyPane( List<Item> properties ) { + this( properties, 0 ); + } + + public PropertyPane( List<Item> properties, int nestingLevel ) { + setVgap(5); + setHgap(5); + setPadding(new Insets(5, 15, 5, 15 + nestingLevel*10 )); + getStyleClass().add("property-pane"); //$NON-NLS-1$ + setItems(properties); +// setGridLinesVisible(true); + } + + public void setItems( List<Item> properties ) { + getChildren().clear(); + + String filter = getSkinnable().titleFilter().get(); + filter = filter == null? "": filter.trim().toLowerCase(); //$NON-NLS-1$ + + int row = 0; + + for (Item item : properties) { + + // filter properties + String title = item.getName(); + + if ( !filter.isEmpty() && title.toLowerCase().indexOf( filter ) < 0) continue; + + // setup property label + Label label = new Label(title); + label.setMinWidth(MIN_COLUMN_WIDTH); + + // show description as a tooltip + String description = item.getDescription(); + if ( description != null && !description.trim().isEmpty()) { + label.setTooltip(new Tooltip(description)); + } + + add(label, 0, row); + + // setup property editor + Node editor = getEditor(item); + + if (editor instanceof Region) { + ((Region)editor).setMinWidth(MIN_COLUMN_WIDTH); + ((Region)editor).setMaxWidth(Double.MAX_VALUE); + } + label.setLabelFor(editor); + add(editor, 1, row); + GridPane.setHgrow(editor, Priority.ALWAYS); + + //TODO add support for recursive properties + + row++; + } + + } + + @SuppressWarnings("unchecked") + private Node getEditor(Item item) { + @SuppressWarnings("rawtypes") + PropertyEditor editor = getSkinnable().getPropertyEditorFactory().call(item); + if (editor == null) { + editor = new AbstractPropertyEditor<Object, TextField>(item, new TextField(), true) { + { + getEditor().setEditable(false); + getEditor().setDisable(true); + } + + /** + * {@inheritDoc} + */ + @Override protected ObservableValue<Object> getObservableValue() { + return (ObservableValue<Object>)(Object)getEditor().textProperty(); + } + + /** + * {@inheritDoc} + */ + @Override public void setValue(Object value) { + getEditor().setText(value == null? "": value.toString()); //$NON-NLS-1$ + } + }; + } else if (! item.isEditable()) { + editor.getEditor().setDisable(true); + } + editor.setValue(item.getValue()); + return editor.getEditor(); + } + } +} diff --git a/src/impl/org/controlsfx/skin/RangeSliderSkin.java b/src/impl/org/controlsfx/skin/RangeSliderSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..2b324924b5e4516671b856c4dbf2e29fdb90463b --- /dev/null +++ b/src/impl/org/controlsfx/skin/RangeSliderSkin.java @@ -0,0 +1,580 @@ +/** + * Copyright (c) 2013, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import static impl.org.controlsfx.behavior.RangeSliderBehavior.FocusedChild.HIGH_THUMB; +import static impl.org.controlsfx.behavior.RangeSliderBehavior.FocusedChild.LOW_THUMB; +import static impl.org.controlsfx.behavior.RangeSliderBehavior.FocusedChild.NONE; +import static impl.org.controlsfx.behavior.RangeSliderBehavior.FocusedChild.RANGE_BAR; +import impl.org.controlsfx.behavior.RangeSliderBehavior; +import impl.org.controlsfx.behavior.RangeSliderBehavior.FocusedChild; +import javafx.beans.binding.ObjectBinding; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Side; +import javafx.scene.Cursor; +import javafx.scene.chart.NumberAxis; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.util.Callback; + +import org.controlsfx.control.RangeSlider; + +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; +import com.sun.javafx.scene.traversal.Direction; +import com.sun.javafx.scene.traversal.ParentTraversalEngine; + +public class RangeSliderSkin extends BehaviorSkinBase<RangeSlider, RangeSliderBehavior> { + + /** Track if slider is vertical/horizontal and cause re layout */ + private NumberAxis tickLine = null; + private double trackToTickGap = 2; + + private boolean showTickMarks; + private double thumbWidth; + private double thumbHeight; + + private Orientation orientation; + + private StackPane track; + private double trackStart; + private double trackLength; + private double lowThumbPos; + private double rangeEnd; + private double rangeStart; + private ThumbPane lowThumb; + private ThumbPane highThumb; + private StackPane rangeBar; // the bar between the two thumbs, can be dragged + + // temp fields for mouse drag handling + private double preDragPos; // used as a temp value for low and high thumbs + private Point2D preDragThumbPoint; // in skin coordinates + + private FocusedChild currentFocus = LOW_THUMB; + + public RangeSliderSkin(final RangeSlider rangeSlider) { + super(rangeSlider, new RangeSliderBehavior(rangeSlider)); + orientation = getSkinnable().getOrientation(); + initFirstThumb(); + initSecondThumb(); + initRangeBar(); + registerChangeListener(rangeSlider.lowValueProperty(), "LOW_VALUE"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.highValueProperty(), "HIGH_VALUE"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.minProperty(), "MIN"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.maxProperty(), "MAX"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.orientationProperty(), "ORIENTATION"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.showTickMarksProperty(), "SHOW_TICK_MARKS"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.showTickLabelsProperty(), "SHOW_TICK_LABELS"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.majorTickUnitProperty(), "MAJOR_TICK_UNIT"); //$NON-NLS-1$ + registerChangeListener(rangeSlider.minorTickCountProperty(), "MINOR_TICK_COUNT"); //$NON-NLS-1$ + lowThumb.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean hasFocus) { + if (hasFocus) { + currentFocus = LOW_THUMB; + } + } + }); + highThumb.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean hasFocus) { + if (hasFocus) { + currentFocus = HIGH_THUMB; + } + } + }); + rangeBar.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean hasFocus) { + if (hasFocus) { + currentFocus = RANGE_BAR; + } + } + }); + rangeSlider.focusedProperty().addListener(new ChangeListener<Boolean>() { + @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean hasFocus) { + if (hasFocus) { + lowThumb.setFocus(true); + } else { + lowThumb.setFocus(false); + highThumb.setFocus(false); + currentFocus = NONE; + } + } + }); + + EventHandler<KeyEvent> keyEventHandler = new EventHandler<KeyEvent>() { + @Override public void handle(KeyEvent event) { + if (KeyCode.TAB.equals(event.getCode())) { + if (lowThumb.isFocused()) { + if (event.isShiftDown()) { + lowThumb.setFocus(false); + new ParentTraversalEngine(rangeSlider).select(rangeSlider, Direction.PREVIOUS); + } else { + lowThumb.setFocus(false); + highThumb.setFocus(true); + } + event.consume(); + } else if (highThumb.isFocused()) { + if(event.isShiftDown()) { + highThumb.setFocus(false); + lowThumb.setFocus(true); + } else { + highThumb.setFocus(false); + new ParentTraversalEngine(rangeSlider).select(rangeSlider, Direction.NEXT); + } + event.consume(); + } + } + } + }; + getSkinnable().addEventHandler(KeyEvent.KEY_PRESSED, keyEventHandler); + // set up a callback on the behavior to indicate which thumb is currently + // selected (via enum). + getBehavior().setSelectedValue(new Callback<Void, FocusedChild>() { + @Override public FocusedChild call(Void v) { + return currentFocus; + } + }); + } + + private void initFirstThumb() { + lowThumb = new ThumbPane(); + lowThumb.getStyleClass().setAll("low-thumb"); //$NON-NLS-1$ + lowThumb.setFocusTraversable(true); + track = new StackPane(); + track.getStyleClass().setAll("track"); //$NON-NLS-1$ + + getChildren().clear(); + getChildren().addAll(track, lowThumb); + setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels()); + track.setOnMousePressed( new EventHandler<javafx.scene.input.MouseEvent>() { + @Override public void handle(javafx.scene.input.MouseEvent me) { + if (!lowThumb.isPressed() && !highThumb.isPressed()) { + if (isHorizontal()) { + getBehavior().trackPress(me, (me.getX() / trackLength)); + } else { + getBehavior().trackPress(me, (me.getY() / trackLength)); + } + } + } + }); + + track.setOnMouseReleased( new EventHandler<javafx.scene.input.MouseEvent>() { + @Override public void handle(javafx.scene.input.MouseEvent me) { + //Nothing being done with the second param in sliderBehavior + //So, passing a dummy value + getBehavior().trackRelease(me, 0.0f); + } + }); + + lowThumb.setOnMousePressed(new EventHandler<javafx.scene.input.MouseEvent>() { + @Override public void handle(javafx.scene.input.MouseEvent me) { + highThumb.setFocus(false); + lowThumb.setFocus(true); + getBehavior().lowThumbPressed(me, 0.0f); + preDragThumbPoint = lowThumb.localToParent(me.getX(), me.getY()); + preDragPos = (getSkinnable().getLowValue() - getSkinnable().getMin()) / + (getMaxMinusMinNoZero()); + } + }); + + lowThumb.setOnMouseReleased(new EventHandler<javafx.scene.input.MouseEvent>() { + @Override public void handle(javafx.scene.input.MouseEvent me) { + getBehavior().lowThumbReleased(me); + } + }); + + lowThumb.setOnMouseDragged(new EventHandler<javafx.scene.input.MouseEvent>() { + @Override public void handle(javafx.scene.input.MouseEvent me) { + Point2D cur = lowThumb.localToParent(me.getX(), me.getY()); + double dragPos = (isHorizontal())? + cur.getX() - preDragThumbPoint.getX() : -(cur.getY() - preDragThumbPoint.getY()); + getBehavior().lowThumbDragged(me, preDragPos + dragPos / trackLength); + } + }); + } + + private void initSecondThumb() { + highThumb = new ThumbPane(); + highThumb.getStyleClass().setAll("high-thumb"); //$NON-NLS-1$ + highThumb.setFocusTraversable(true); + if (!getChildren().contains(highThumb)) { + getChildren().add(highThumb); + } + + highThumb.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + lowThumb.setFocus(false); + highThumb.setFocus(true); + ((RangeSliderBehavior) getBehavior()).highThumbPressed(e, 0.0D); + preDragThumbPoint = highThumb.localToParent(e.getX(), e.getY()); + preDragPos = (((RangeSlider) getSkinnable()).getHighValue() - ((RangeSlider) getSkinnable()).getMin()) / + (getMaxMinusMinNoZero()); + } + } + ); + highThumb.setOnMouseReleased(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + ((RangeSliderBehavior) getBehavior()).highThumbReleased(e); + } + } + ); + highThumb.setOnMouseDragged(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + boolean orientation = ((RangeSlider) getSkinnable()).getOrientation() == Orientation.HORIZONTAL; + double trackLength = orientation ? track.getWidth() : track.getHeight(); + + Point2D point2d = highThumb.localToParent(e.getX(), e.getY()); + double d = ((RangeSlider) getSkinnable()).getOrientation() != Orientation.HORIZONTAL ? -(point2d.getY() - preDragThumbPoint.getY()) : point2d.getX() - preDragThumbPoint.getX(); + ((RangeSliderBehavior) getBehavior()).highThumbDragged(e, preDragPos + d / trackLength); + } + }); + } + + private void initRangeBar() { + rangeBar = new StackPane(); + rangeBar.cursorProperty().bind(new ObjectBinding<Cursor>() { + { bind(rangeBar.hoverProperty()); } + + @Override protected Cursor computeValue() { + return rangeBar.isHover() ? Cursor.HAND : Cursor.DEFAULT; + } + }); + rangeBar.getStyleClass().setAll("range-bar"); //$NON-NLS-1$ + + rangeBar.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + rangeBar.requestFocus(); + preDragPos = isHorizontal() ? e.getX() : -e.getY(); + } + }); + + rangeBar.setOnMouseDragged(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + double delta = (isHorizontal() ? e.getX() : -e.getY()) - preDragPos; + ((RangeSliderBehavior) getBehavior()).moveRange(delta); + } + }); + + rangeBar.setOnMouseReleased(new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent e) { + ((RangeSliderBehavior) getBehavior()).confirmRange(); + } + }); + + getChildren().add(rangeBar); + } + + /** + * When ticks or labels are changing of visibility, we compute the new + * visibility and add the necessary objets. After this method, we must be + * sure to add the high Thumb and the rangeBar. + * + * @param ticksVisible + * @param labelsVisible + */ + private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) { + showTickMarks = (ticksVisible || labelsVisible); + RangeSlider rangeSlider = getSkinnable(); + if (showTickMarks) { + if (tickLine == null) { + tickLine = new NumberAxis(); + tickLine.tickLabelFormatterProperty().bind(getSkinnable().labelFormatterProperty()); + tickLine.setAnimated(false); + tickLine.setAutoRanging(false); + tickLine.setSide(isHorizontal() ? Side.BOTTOM : Side.RIGHT); + tickLine.setUpperBound(rangeSlider.getMax()); + tickLine.setLowerBound(rangeSlider.getMin()); + tickLine.setTickUnit(rangeSlider.getMajorTickUnit()); + tickLine.setTickMarkVisible(ticksVisible); + tickLine.setTickLabelsVisible(labelsVisible); + tickLine.setMinorTickVisible(ticksVisible); + // add 1 to the slider minor tick count since the axis draws one + // less minor ticks than the number given. + tickLine.setMinorTickCount(Math.max(rangeSlider.getMinorTickCount(),0) + 1); + getChildren().clear(); + getChildren().addAll(tickLine, track, lowThumb); + } else { + tickLine.setTickLabelsVisible(labelsVisible); + tickLine.setTickMarkVisible(ticksVisible); + tickLine.setMinorTickVisible(ticksVisible); + } + } + else { + getChildren().clear(); + getChildren().addAll(track, lowThumb); +// tickLine = null; + } + + getSkinnable().requestLayout(); + } + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + if ("ORIENTATION".equals(p)) { //$NON-NLS-1$ + orientation = getSkinnable().getOrientation(); + if (showTickMarks && tickLine != null) { + tickLine.setSide(isHorizontal() ? Side.BOTTOM : Side.RIGHT); + } + getSkinnable().requestLayout(); + } else if ("MIN".equals(p) ) { //$NON-NLS-1$ + if (showTickMarks && tickLine != null) { + tickLine.setLowerBound(getSkinnable().getMin()); + } + getSkinnable().requestLayout(); + } else if ("MAX".equals(p)) { //$NON-NLS-1$ + if (showTickMarks && tickLine != null) { + tickLine.setUpperBound(getSkinnable().getMax()); + } + getSkinnable().requestLayout(); + } else if ("SHOW_TICK_MARKS".equals(p) || "SHOW_TICK_LABELS".equals(p)) { //$NON-NLS-1$ //$NON-NLS-2$ + setShowTickMarks(getSkinnable().isShowTickMarks(), getSkinnable().isShowTickLabels()); + if (!getChildren().contains(highThumb)) + getChildren().add(highThumb); + if (!getChildren().contains(rangeBar)) + getChildren().add(rangeBar); + } else if ("MAJOR_TICK_UNIT".equals(p)) { //$NON-NLS-1$ + if (tickLine != null) { + tickLine.setTickUnit(getSkinnable().getMajorTickUnit()); + getSkinnable().requestLayout(); + } + } else if ("MINOR_TICK_COUNT".equals(p)) { //$NON-NLS-1$ + if (tickLine != null) { + tickLine.setMinorTickCount(Math.max(getSkinnable().getMinorTickCount(),0) + 1); + getSkinnable().requestLayout(); + } + } else if ("LOW_VALUE".equals(p)) { //$NON-NLS-1$ + positionLowThumb(); + rangeBar.resizeRelocate(rangeStart, rangeBar.getLayoutY(), + rangeEnd - rangeStart, rangeBar.getHeight()); + } else if ("HIGH_VALUE".equals(p)) { //$NON-NLS-1$ + positionHighThumb(); + rangeBar.resize(rangeEnd-rangeStart, rangeBar.getHeight()); + } + super.handleControlPropertyChanged(p); + } + + /** + * + * @return the difference between max and min, but if they have the same + * value, 1 is returned instead of 0 because otherwise the division where it + * can be used will return Nan. + */ + private double getMaxMinusMinNoZero() { + RangeSlider s = getSkinnable(); + return s.getMax() - s.getMin() == 0 ? 1 : s.getMax() - s.getMin(); + } + + /** + * Called when ever either min, max or lowValue changes, so lowthumb's layoutX, Y is recomputed. + */ + private void positionLowThumb() { + RangeSlider s = getSkinnable(); + boolean horizontal = isHorizontal(); + double lx = (horizontal) ? trackStart + (((trackLength * ((s.getLowValue() - s.getMin()) / + (getMaxMinusMinNoZero()))) - thumbWidth/2)) : lowThumbPos; + double ly = (horizontal) ? lowThumbPos : + getSkinnable().getInsets().getTop() + trackLength - (trackLength * ((s.getLowValue() - s.getMin()) / + (getMaxMinusMinNoZero()))); // - thumbHeight/2 + lowThumb.setLayoutX(lx); + lowThumb.setLayoutY(ly); + if (horizontal) rangeStart = lx + thumbWidth; else rangeEnd = ly; + } + + /** + * Called when ever either min, max or highValue changes, so highthumb's layoutX, Y is recomputed. + */ + private void positionHighThumb() { + RangeSlider slider = (RangeSlider) getSkinnable(); + boolean orientation = ((RangeSlider) getSkinnable()).getOrientation() == Orientation.HORIZONTAL; + + double thumbWidth = lowThumb.getWidth(); + double thumbHeight = lowThumb.getHeight(); + highThumb.resize(thumbWidth, thumbHeight); + + double pad = 0;//track.impl_getBackgroundFills() == null || track.impl_getBackgroundFills().length <= 0 ? 0.0D : track.impl_getBackgroundFills()[0].getTopLeftCornerRadius(); + double trackStart = orientation ? track.getLayoutX() : track.getLayoutY(); + trackStart += pad; + double trackLength = orientation ? track.getWidth() : track.getHeight(); + trackLength -= 2 * pad; + + double x = orientation ? trackStart + (trackLength * ((slider.getHighValue() - slider.getMin()) / (getMaxMinusMinNoZero())) - thumbWidth / 2D) : lowThumb.getLayoutX(); + double y = orientation ? lowThumb.getLayoutY() : (getSkinnable().getInsets().getTop() + trackLength) - trackLength * ((slider.getHighValue() - slider.getMin()) / (getMaxMinusMinNoZero())); + highThumb.setLayoutX(x); + highThumb.setLayoutY(y); + if (orientation) rangeEnd = x; else rangeStart = y + thumbWidth; + } + + @Override protected void layoutChildren(final double x, final double y, + final double w, final double h) { + // resize thumb to preferred size + thumbWidth = lowThumb.prefWidth(-1); + thumbHeight = lowThumb.prefHeight(-1); + lowThumb.resize(thumbWidth, thumbHeight); + // we are assuming the is common radius's for all corners on the track + double trackRadius = track.getBackground() == null ? 0 : track.getBackground().getFills().size() > 0 ? + track.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius() : 0; + + if (isHorizontal()) { + double tickLineHeight = (showTickMarks) ? tickLine.prefHeight(-1) : 0; + double trackHeight = track.prefHeight(-1); + double trackAreaHeight = Math.max(trackHeight,thumbHeight); + double totalHeightNeeded = trackAreaHeight + ((showTickMarks) ? trackToTickGap+tickLineHeight : 0); + double startY = y + ((h - totalHeightNeeded)/2); // center slider in available height vertically + trackLength = w - thumbWidth; + trackStart = x + (thumbWidth/2); + double trackTop = (int)(startY + ((trackAreaHeight-trackHeight)/2)); + lowThumbPos = (int)(startY + ((trackAreaHeight-thumbHeight)/2)); + + positionLowThumb(); + // layout track + track.resizeRelocate(trackStart - trackRadius, trackTop , trackLength + trackRadius + trackRadius, trackHeight); + positionHighThumb(); + // layout range bar + rangeBar.resizeRelocate(rangeStart, trackTop, rangeEnd - rangeStart, trackHeight); + // layout tick line + if (showTickMarks) { + tickLine.setLayoutX(trackStart); + tickLine.setLayoutY(trackTop+trackHeight+trackToTickGap); + tickLine.resize(trackLength, tickLineHeight); + tickLine.requestAxisLayout(); + } else { + if (tickLine != null) { + tickLine.resize(0,0); + tickLine.requestAxisLayout(); + } + tickLine = null; + } + } else { + double tickLineWidth = (showTickMarks) ? tickLine.prefWidth(-1) : 0; + double trackWidth = track.prefWidth(-1); + double trackAreaWidth = Math.max(trackWidth,thumbWidth); + double totalWidthNeeded = trackAreaWidth + ((showTickMarks) ? trackToTickGap+tickLineWidth : 0) ; + double startX = x + ((w - totalWidthNeeded)/2); // center slider in available width horizontally + trackLength = h - thumbHeight; + trackStart = y + (thumbHeight/2); + double trackLeft = (int)(startX + ((trackAreaWidth-trackWidth)/2)); + lowThumbPos = (int)(startX + ((trackAreaWidth-thumbWidth)/2)); + + positionLowThumb(); + // layout track + track.resizeRelocate(trackLeft, trackStart - trackRadius, trackWidth, trackLength + trackRadius + trackRadius); + positionHighThumb(); + // layout range bar + rangeBar.resizeRelocate(trackLeft, rangeStart, trackWidth, rangeEnd - rangeStart); + // layout tick line + if (showTickMarks) { + tickLine.setLayoutX(trackLeft+trackWidth+trackToTickGap); + tickLine.setLayoutY(trackStart); + tickLine.resize(tickLineWidth, trackLength); + tickLine.requestAxisLayout(); + } else { + if (tickLine != null) { + tickLine.resize(0,0); + tickLine.requestAxisLayout(); + } + tickLine = null; + } + } + } + + private double minTrackLength() { + return 2*lowThumb.prefWidth(-1); + } + + @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + if (isHorizontal()) { + return (leftInset + minTrackLength() + lowThumb.minWidth(-1) + rightInset); + } else { + return (leftInset + lowThumb.prefWidth(-1) + rightInset); + } + } + + @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + if (isHorizontal()) { + return (topInset + lowThumb.prefHeight(-1) + bottomInset); + } else { + return (topInset + minTrackLength() + lowThumb.prefHeight(-1) + bottomInset); + } + } + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + if (isHorizontal()) { + if(showTickMarks) { + return Math.max(140, tickLine.prefWidth(-1)); + } else { + return 140; + } + } else { + //return (padding.getLeft()) + Math.max(thumb.prefWidth(-1), track.prefWidth(-1)) + padding.getRight(); + return leftInset + Math.max(lowThumb.prefWidth(-1), track.prefWidth(-1)) + + ((showTickMarks) ? (trackToTickGap+tickLine.prefWidth(-1)) : 0) + rightInset; + } + } + + @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + if (isHorizontal()) { + return getSkinnable().getInsets().getTop() + Math.max(lowThumb.prefHeight(-1), track.prefHeight(-1)) + + ((showTickMarks) ? (trackToTickGap+tickLine.prefHeight(-1)) : 0) + bottomInset; + } else { + if(showTickMarks) { + return Math.max(140, tickLine.prefHeight(-1)); + } else { + return 140; + } + } + } + + @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + if (isHorizontal()) { + return Double.MAX_VALUE; + } else { + return getSkinnable().prefWidth(-1); + } + } + + @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + if (isHorizontal()) { + return getSkinnable().prefHeight(width); + } else { + return Double.MAX_VALUE; + } + } + + private boolean isHorizontal() { + return orientation == null || orientation == Orientation.HORIZONTAL; + } + + private static class ThumbPane extends StackPane { + public void setFocus(boolean value) { + setFocused(value); + } + } +} diff --git a/src/impl/org/controlsfx/skin/RatingSkin.java b/src/impl/org/controlsfx/skin/RatingSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..8c13fc31320e3f6bbdf06324dd83d4bd4122d0b5 --- /dev/null +++ b/src/impl/org/controlsfx/skin/RatingSkin.java @@ -0,0 +1,329 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import impl.org.controlsfx.behavior.RatingBehavior; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.event.EventHandler; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Rectangle; + +import org.controlsfx.control.Rating; +import org.controlsfx.tools.Utils; + +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +/** + * + */ +public class RatingSkin extends BehaviorSkinBase<Rating, RatingBehavior> { + + /*************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private static final String STRONG = "strong"; //$NON-NLS-1$ + + private boolean updateOnHover; + private boolean partialRating; + + // the container for the traditional rating control. If updateOnHover and + // partialClipping are disabled, this will show a combination of strong + // and non-strong graphics, depending on the current rating value + private Pane backgroundContainer; + + // the container for the strong graphics which may be partially clipped. + // Note that this only exists if updateOnHover or partialClipping is enabled. + private Pane foregroundContainer; + + private double rating = -1; + + private Rectangle forgroundClipRect; + + private final EventHandler<MouseEvent> mouseMoveHandler = new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent event) { + + // if we support updateOnHover, calculate the intended rating based on the mouse + // location and update the control property with it. + + if (updateOnHover) { + updateRatingFromMouseEvent(event); + } + } + }; + + private final EventHandler<MouseEvent> mouseClickHandler = new EventHandler<MouseEvent>() { + @Override public void handle(MouseEvent event) { + + // if we are not updating on hover, calculate the intended rating based on the mouse + // location and update the control property with it. + + if (! updateOnHover) { + updateRatingFromMouseEvent(event); + } + } + }; + + private void updateRatingFromMouseEvent(MouseEvent event) { + Rating control = getSkinnable(); + if (! control.ratingProperty().isBound()) { + Point2D mouseLocation = new Point2D(event.getSceneX(), event.getSceneY()); + control.setRating(calculateRating(mouseLocation)); + } + } + + /*************************************************************************** + * + * Constructors + * + **************************************************************************/ + + public RatingSkin(Rating control) { + super(control, new RatingBehavior(control)); + + this.updateOnHover = control.isUpdateOnHover(); + this.partialRating = control.isPartialRating(); + + // init + recreateButtons(); + updateRating(); + // -- end init + + registerChangeListener(control.ratingProperty(), "RATING"); //$NON-NLS-1$ + registerChangeListener(control.maxProperty(), "MAX"); //$NON-NLS-1$ + registerChangeListener(control.orientationProperty(), "ORIENTATION"); //$NON-NLS-1$ + registerChangeListener(control.updateOnHoverProperty(), "UPDATE_ON_HOVER"); //$NON-NLS-1$ + registerChangeListener(control.partialRatingProperty(), "PARTIAL_RATING"); //$NON-NLS-1$ + // added to ensure clip is correctly calculated when control is first shown: + registerChangeListener(control.boundsInLocalProperty(), "BOUNDS"); //$NON-NLS-1$ + } + + + + /*************************************************************************** + * + * Implementation + * + **************************************************************************/ + + @Override protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if (p == "RATING") { //$NON-NLS-1$ + updateRating(); + } else if (p == "MAX") { //$NON-NLS-1$ + recreateButtons(); + } else if (p == "ORIENTATION") { //$NON-NLS-1$ + recreateButtons(); + } else if (p == "PARTIAL_RATING") { //$NON-NLS-1$ + this.partialRating = getSkinnable().isPartialRating(); + recreateButtons(); + } else if (p == "UPDATE_ON_HOVER") { //$NON-NLS-1$ + this.updateOnHover = getSkinnable().isUpdateOnHover(); + recreateButtons(); + } else if (p == "BOUNDS") { //$NON-NLS-1$ + if (this.partialRating) { + updateClip(); + } + } + } + + private void recreateButtons() { + backgroundContainer = null; + foregroundContainer = null; + + backgroundContainer = isVertical() ? new VBox() : new HBox(); + backgroundContainer.getStyleClass().add("container"); //$NON-NLS-1$ + getChildren().setAll(backgroundContainer); + + if (updateOnHover || partialRating) { + foregroundContainer = isVertical() ? new VBox() : new HBox(); + foregroundContainer.getStyleClass().add("container"); //$NON-NLS-1$ + foregroundContainer.setMouseTransparent(true); + getChildren().add(foregroundContainer); + + forgroundClipRect = new Rectangle(); + foregroundContainer.setClip(forgroundClipRect); + + } + + for (int index = 0; index <= getSkinnable().getMax(); index++) { + Node backgroundNode = createButton(); + + if (index > 0) { + if (isVertical()) { + backgroundContainer.getChildren().add(0,backgroundNode); + } else { + backgroundContainer.getChildren().add(backgroundNode); + } + + if (partialRating) { + Node foregroundNode = createButton(); + foregroundNode.getStyleClass().add(STRONG); + foregroundNode.setMouseTransparent(true); + + if (isVertical()) { + foregroundContainer.getChildren().add(0,foregroundNode); + } else { + foregroundContainer.getChildren().add(foregroundNode); + } + } + } + } + + updateRating(); + } + + // Calculate the rating based on a mouse position (in Scene coordinates). + // If we support partial ratings, the value is calculated directly. + // Otherwise the ceil of the value is computed. + private double calculateRating(Point2D sceneLocation) { + final Point2D b = backgroundContainer.sceneToLocal(sceneLocation); + + final double x = b.getX(); + final double y = b.getY(); + + final Rating control = getSkinnable(); + + final int max = control.getMax(); + final double w = control.getWidth() - (snappedLeftInset() + snappedRightInset()); + final double h = control.getHeight() - (snappedTopInset() + snappedBottomInset()); + + double newRating = -1; + + if (isVertical()) { + newRating = ((h - y) / h) * max; + } else { + newRating = (x / w) * max; + } + + if (! partialRating) { + newRating = Utils.clamp(1, Math.ceil(newRating), control.getMax()); + } + + return newRating; + } + + private void updateClip() { + final Rating control = getSkinnable(); + final double h = control.getHeight() - (snappedTopInset() + snappedBottomInset()); + final double w = control.getWidth() - (snappedLeftInset() + snappedRightInset()); + + if (isVertical()) { + final double y = h * rating / control.getMax() ; + forgroundClipRect.relocate(0, h - y); + forgroundClipRect.setWidth(control.getWidth()); + forgroundClipRect.setHeight(y); + } else { + final double x = w * rating / control.getMax(); + forgroundClipRect.setWidth(x); + forgroundClipRect.setHeight(control.getHeight()); + } + + } + +// private double getSpacing() { +// return (backgroundContainer instanceof HBox) ? +// ((HBox)backgroundContainer).getSpacing() : +// ((VBox)backgroundContainer).getSpacing(); +// } + + private Node createButton() { + Region btn = new Region(); + btn.getStyleClass().add("button"); //$NON-NLS-1$ + + btn.setOnMouseMoved(mouseMoveHandler); + btn.setOnMouseClicked(mouseClickHandler); + return btn; + } + + // Update the skin based on a new value for the rating. + // If we support partial ratings, updates the clip. + // Otherwise, updates the style classes for the buttons. + + private void updateRating() { + + double newRating = getSkinnable().getRating(); + + if (newRating == rating) return; + + rating = Utils.clamp(0, newRating, getSkinnable().getMax()); + + if (partialRating) { + updateClip(); + } else { + updateButtonStyles(); + } + } + + private void updateButtonStyles() { + final int max = getSkinnable().getMax(); + + // make a copy of the buttons list so that we can reverse the order if + // the list is vertical (as the buttons are ordered bottom to top). + List<Node> buttons = new ArrayList<>(backgroundContainer.getChildren()); + if (isVertical()) { + Collections.reverse(buttons); + } + + for (int i = 0; i < max; i++) { + Node button = buttons.get(i); + + final List<String> styleClass = button.getStyleClass(); + final boolean containsStrong = styleClass.contains(STRONG); + + if (i < rating) { + if (! containsStrong) { + styleClass.add(STRONG); + } + } else if (containsStrong) { + styleClass.remove(STRONG); + } + } + } + + private boolean isVertical() { + return getSkinnable().getOrientation() == Orientation.VERTICAL; + } + + @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); + } +} diff --git a/src/impl/org/controlsfx/skin/SegmentedButtonSkin.java b/src/impl/org/controlsfx/skin/SegmentedButtonSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..ebb921ebcd9a0b7b230efdc59d09e888599f41cc --- /dev/null +++ b/src/impl/org/controlsfx/skin/SegmentedButtonSkin.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import java.util.Collections; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.collections.ObservableList; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.HBox; + +import org.controlsfx.control.SegmentedButton; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; +import com.sun.javafx.scene.control.behavior.KeyBinding; +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +public class SegmentedButtonSkin extends BehaviorSkinBase<SegmentedButton, BehaviorBase<SegmentedButton>> { + + private static final String ONLY_BUTTON = "only-button"; //$NON-NLS-1$ + private static final String LEFT_PILL = "left-pill"; //$NON-NLS-1$ + private static final String CENTER_PILL = "center-pill"; //$NON-NLS-1$ + private static final String RIGHT_PILL = "right-pill"; //$NON-NLS-1$ + + private final HBox container; + + public SegmentedButtonSkin(SegmentedButton control) { + super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); + + container = new HBox(); + + getChildren().add(container); + + updateButtons(); + getButtons().addListener(new InvalidationListener() { + @Override public void invalidated(Observable observable) { + updateButtons(); + } + }); + + control.toggleGroupProperty().addListener((observable, oldValue, newValue) -> { + getButtons().forEach((button) -> { + button.setToggleGroup(newValue); + }); + }); + } + + private ObservableList<ToggleButton> getButtons() { + return getSkinnable().getButtons(); + } + + private void updateButtons() { + ObservableList<ToggleButton> buttons = getButtons(); + ToggleGroup group = getSkinnable().getToggleGroup(); + + container.getChildren().clear(); + + for (int i = 0; i < getButtons().size(); i++) { + ToggleButton t = buttons.get(i); + + if (group != null) { + t.setToggleGroup(group); + } + + t.getStyleClass().removeAll(ONLY_BUTTON, LEFT_PILL, CENTER_PILL, RIGHT_PILL); + container.getChildren().add(t); + + if (i == buttons.size() - 1) { + if (i == 0) { + t.getStyleClass().add(ONLY_BUTTON); + } else { + t.getStyleClass().add(RIGHT_PILL); + } + } else if (i == 0) { + t.getStyleClass().add(LEFT_PILL); + } else { + t.getStyleClass().add(CENTER_PILL); + } + } + } + + @Override + protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().prefWidth(height); + } + + @Override + protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().prefHeight(width); + } +} diff --git a/src/impl/org/controlsfx/skin/SnapshotViewSkin.java b/src/impl/org/controlsfx/skin/SnapshotViewSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..5bdf23ef23ce73fc621a485f22740b26703b3aa2 --- /dev/null +++ b/src/impl/org/controlsfx/skin/SnapshotViewSkin.java @@ -0,0 +1,566 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import impl.org.controlsfx.behavior.SnapshotViewBehavior; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.Bounds; +import javafx.geometry.Pos; +import javafx.geometry.Rectangle2D; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.StrokeType; + +import org.controlsfx.control.SnapshotView; +import org.controlsfx.control.SnapshotView.Boundary; + +import com.sun.javafx.scene.control.skin.BehaviorSkinBase; + +/** + * View for the {@link SnapshotView}. It displays the node and the selection and manages their positioning. Mouse events + * are handed over to the {@link SnapshotViewBehavior} which uses them to change the selection. + */ +public class SnapshotViewSkin extends BehaviorSkinBase<SnapshotView, SnapshotViewBehavior> { + + /* ************************************************************************ + * * + * Attributes & Properties * + * * + **************************************************************************/ + + /** + * The currently displayed node; when the {@link SnapshotView#nodeProperty() node} property changes + * {@link #updateNode() updateNode} will set the new one. + */ + private Node node; + + /** + * The pane displaying the {@link #node}. + */ + private final GridPane gridPane; + + /** + * The (mutable) rectangle which represents the selected area. + */ + private final Rectangle selectedArea; + + /** + * The rectangle whose stroke represents the unselected area. Binding is used to ensure that the rectangle itself + * always has the same size and position as the {@link #selectedArea}. + */ + private final Rectangle unselectedArea; + + /** + * The node capturing mouse events. + */ + private final Node mouseNode; + + /* ************************************************************************ + * * + * Constructor & Initialization * + * * + **************************************************************************/ + + /** + * Creates a new skin for the specified {@link SnapshotView}. + * + * @param snapshotView + * the {@link SnapshotView} this skin will display + */ + public SnapshotViewSkin(SnapshotView snapshotView) { + + super(snapshotView, new SnapshotViewBehavior(snapshotView)); + + this.gridPane = createGridPane(); + this.selectedArea = new Rectangle(); + this.unselectedArea = new Rectangle(); + this.mouseNode = createMouseNode(); + + buildSceneGraph(); + initializeAreas(); + + registerChangeListener(snapshotView.nodeProperty(), "NODE"); //$NON-NLS-1$ + registerChangeListener(snapshotView.selectionProperty(), "SELECTION"); //$NON-NLS-1$ + } + + @Override + protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if ("NODE".equals(p)) { //$NON-NLS-1$ + updateNode(); + } else if ("SELECTION".equals(p)) { //$NON-NLS-1$ + updateSelection(); + } + } + + /** + * Creates the grid pane which will contain the node. + * + * @return a {@link GridPane} + */ + private static GridPane createGridPane() { + GridPane pane = new GridPane(); + pane.setAlignment(Pos.CENTER); + return pane; + } + + /** + * Creates the node which will be used to capture mouse events. Events are handed over to + * {@link #handleMouseEvent(MouseEvent) handleMouseEvent}. + * + * @return a {@link Node} + */ + private Node createMouseNode() { + Rectangle mouseNode = new Rectangle(); + + // make the node transparent and make sure its size does not affect the control's size + mouseNode.setFill(Color.TRANSPARENT); + mouseNode.setManaged(false); + + // bind width and height to the control + mouseNode.widthProperty().bind(getSkinnable().widthProperty()); + mouseNode.heightProperty().bind(getSkinnable().heightProperty()); + + // let it handle the mouse events if allowed by the user + mouseNode.addEventHandler(MouseEvent.ANY, this::handleMouseEvent); + mouseNode.mouseTransparentProperty().bind(getSkinnable().selectionMouseTransparentProperty()); + + return mouseNode; + } + + /** + * Builds this skin's scene graph. + */ + private void buildSceneGraph() { + getChildren().addAll(gridPane, unselectedArea, selectedArea, mouseNode); + updateNode(); + } + + /** + * Initializes the {@link #selectedArea} and the {@link #unselectedArea}. This includes their style and their + * bindings to the {@link SnapshotView#selectionProperty() selection} property. + */ + private void initializeAreas() { + styleAreas(); + bindAreaCoordinatesTogether(); + bindAreaVisibilityToSelection(); + } + + /** + * Styles the selected and unselected area. + */ + private void styleAreas() { + selectedArea.fillProperty().bind(getSkinnable().selectionAreaFillProperty()); + selectedArea.strokeProperty().bind(getSkinnable().selectionBorderPaintProperty()); + selectedArea.strokeWidthProperty().bind(getSkinnable().selectionBorderWidthProperty()); + selectedArea.setStrokeType(StrokeType.OUTSIDE); + // if the control's layout depends on this rectangle, + // the stroke's width messes up the layout if the selection is on the pane's edge + selectedArea.setManaged(false); + selectedArea.setMouseTransparent(true); + + unselectedArea.setFill(Color.TRANSPARENT); + unselectedArea.strokeProperty().bind(getSkinnable().unselectedAreaFillProperty()); + unselectedArea.strokeWidthProperty().bind( + Bindings.max(getSkinnable().widthProperty(), getSkinnable().heightProperty())); + unselectedArea.setStrokeType(StrokeType.OUTSIDE); + // this call is crucial! it prevents the enormous unselected area from messing up the layout + unselectedArea.setManaged(false); + unselectedArea.setMouseTransparent(true); + } + + /** + * Binds the position and size of {@link #unselectedArea} to {@link #selectedArea}. + */ + private void bindAreaCoordinatesTogether() { + unselectedArea.xProperty().bind(selectedArea.xProperty()); + unselectedArea.yProperty().bind(selectedArea.yProperty()); + unselectedArea.widthProperty().bind(selectedArea.widthProperty()); + unselectedArea.heightProperty().bind(selectedArea.heightProperty()); + } + + /** + * Binds the visibility of {@link #selectedArea} and {@link #unselectedArea} to the {@code SnapshotView} 's + * {@link SnapshotView#selectionActiveProperty() selectionActive} and {@link SnapshotView#hasSelectionProperty() + * selectionValid} properties. + */ + @SuppressWarnings("unused") + private void bindAreaVisibilityToSelection() { + ReadOnlyBooleanProperty selectionExists = getSkinnable().hasSelectionProperty(); + ReadOnlyBooleanProperty selectionActive = getSkinnable().selectionActiveProperty(); + BooleanBinding existsAndActive = Bindings.and(selectionExists, selectionActive); + + selectedArea.visibleProperty().bind(existsAndActive); + unselectedArea.visibleProperty().bind(existsAndActive); + + // UGLY WORKAROUND AHEAD! + // The clipper should be created in 'styleAreas' but due to the problem explained in 'Clipper.setClip(Node)' + // it has to be created here where the visibility is determined. + + // clip the unselected area according to the view's property - this is done by a designated inner class + new Clipper(getSkinnable(), unselectedArea, () -> unselectedArea.visibleProperty().bind(existsAndActive)); + } + + /* ************************************************************************ + * * + * Node * + * * + **************************************************************************/ + + /** + * Displays the current {@link SnapshotView#nodeProperty() node}. + */ + private void updateNode() { + if (node != null) { + gridPane.getChildren().remove(node); + } + + node = getSkinnable().getNode(); + if (node != null) { + gridPane.getChildren().add(0, node); + } + } + + /* ************************************************************************ + * * + * Selection * + * * + **************************************************************************/ + + /** + * Updates the position and size of {@link #selectedArea} (and by binding that of {@link #unselectedArea}) to a + * changed selection. + */ + private void updateSelection() { + boolean showSelection = getSkinnable().hasSelection() && getSkinnable().isSelectionActive(); + + if (showSelection) { + // the selection can be properly displayed + Rectangle2D selection = getSkinnable().getSelection(); + setSelection(selection.getMinX(), selection.getMinY(), selection.getWidth(), selection.getHeight()); + } else { + // in this case the selection areas are invisible, + // so the only thing left to do is to make sure their coordinates are not all over the place + // (this is not strictly necessary but makes the skin's state cleaner) + setSelection(0, 0, 0, 0); + } + } + + /** + * Updates the position and size of {@link #selectedArea} (and by binding that of {@link #unselectedArea}) to the + * specified arguments. + * + * @param x + * the new x coordinate of the upper left corner + * @param y + * the new y coordinate of the upper left corner + * @param width + * the new width + * @param height + * the new height + */ + private void setSelection(double x, double y, double width, double height) { + selectedArea.setX(x); + selectedArea.setY(y); + selectedArea.setWidth(width); + selectedArea.setHeight(height); + } + + /* ************************************************************************ + * * + * Mouse Events * + * * + **************************************************************************/ + + /** + * Handles mouse events. + * + * @param event + * the {@link MouseEvent} to handle + */ + private void handleMouseEvent(MouseEvent event) { + Cursor newCursor = getBehavior().handleMouseEvent(event); + mouseNode.setCursor(newCursor); + } + + /* ************************************************************************ + * * + * Inner Classes * + * * + **************************************************************************/ + + /** + * Clips the unselected area to the {@link SnapshotView#unselectedAreaBoundaryProperty() unselectedAreaBoundary}. + * + */ + private static class Clipper { + + /** + * The snapshot view to whose {@link Node#boundsInLocalProperty() boundsInLocal} the {@link #clippedNode} will + * be clipped. + */ + private final SnapshotView snapshotView; + + /** + * The node to which the clips will be added. + */ + private final Node clippedNode; + + /** + * A function which rebinds the clip's visibility after it was unbound. Only necessary because of the workaround + * explained in {@link #setClip(Node) setClip}. + */ + private final Runnable rebindClippedNodeVisibility; + + /** + * The {@link Rectangle} used to clip the {@link #clippedNode} to {@link Boundary#CONTROL}. + */ + private final Rectangle controlClip; + + /** + * The {@link Rectangle} used to clip the {@link #clippedNode} to {@link Boundary#NODE}. + */ + private final Rectangle nodeClip; + + /** + * A listener which updates the {@link #controlClip} when the {@link #snapshotView}'s + * {@link Node#boundsInLocalProperty() boundsInLocal} change. + */ + private final ChangeListener<Bounds> updateControlClipToNewBoundsListener; + + /** + * A listener which updates the {@link #nodeClip} when the {@link SnapshotView#nodeProperty() node}'s + * {@link Node#boundsInParentProperty() boundsInParent} change. + */ + private final ChangeListener<Bounds> updateNodeClipToNewBoundsListener; + + /** + * Creates a new clipper with the specified arguments. + * + * @param snapshotView + * the {@link SnapshotView} to whose bounds the {@code clippedNode} will be clipped + * @param clippedNode + * the {@link Node} whose bounds will be clipped + * @param rebindClippedNodeVisibility + * a function which rebinds the {@code clippedNode}'s visibility + */ + public Clipper(SnapshotView snapshotView, Node clippedNode, Runnable rebindClippedNodeVisibility) { + this.snapshotView = snapshotView; + this.clippedNode = clippedNode; + this.rebindClippedNodeVisibility = rebindClippedNodeVisibility; + + // for 'CONTROL', clip to the control's bounds + controlClip = new Rectangle(); + updateControlClipToNewBoundsListener = + (o, oldBounds, newBounds) -> resizeRectangleToBounds(controlClip, newBounds); + + // for 'NODE', clip to the node's bounds + nodeClip = new Rectangle(); + // create the listener which will resize the rectangle + updateNodeClipToNewBoundsListener = + (o, oldBounds, newBounds) -> resizeRectangleToBounds(nodeClip, newBounds); + + // set the clipping and keep updating it + setClipping(); + snapshotView.unselectedAreaBoundaryProperty().addListener((o, oldBoundary, newBoundary) -> setClipping()); + } + + /** + * Sets clipping to the current {@link SnapshotView#unselectedAreaBoundaryProperty() unselectedAreaBoundary}. + */ + private void setClipping() { + Boundary boundary = snapshotView.getUnselectedAreaBoundary(); + switch (boundary) { + case CONTROL: + clipToControl(); + break; + case NODE: + clipToNode(); + break; + default: + throw new IllegalArgumentException("The boundary " + boundary + " is not fully implemented."); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Clips the {@link #clippedNode} to {@link #controlClip} and keeps updating the latter when the control changes + * its bounds. + */ + private void clipToControl() { + // stop resizing the node clip + updateNodeClipToChangingNode(snapshotView.nodeProperty(), snapshotView.getNode(), null); + + // resize the control clip and keep doing so + resizeRectangleToBounds(controlClip, snapshotView.getBoundsInLocal()); + snapshotView.boundsInLocalProperty().addListener(updateControlClipToNewBoundsListener); + + // set the clip + setClip(controlClip); + } + + /** + * Clips the {@link #clippedNode} to {@link #nodeClip} and keeps updating the latter when the control changes + * its bounds. + */ + private void clipToNode() { + // update the node clip to the new bounds and whenever the node changes its bounds + updateNodeClipToChangingNode(snapshotView.nodeProperty(), null, snapshotView.getNode()); + // move that listener from old to new nodes + snapshotView.nodeProperty().addListener(this::updateNodeClipToChangingNode); + + // set the clip + setClip(nodeClip); + } + + /** + * Resizes the {@link #nodeClip} to the specified new node's {@link Node#boundsInParentProperty() + * boundsInParent} (or to an empty rectangle if it is {@code null}) and moves the + * {@link #updateNodeClipToNewBoundsListener} from the old to the new node's {@code boundInParents} property. + * <p> + * Designed to be used as a lambda method reference. + * + * @param o + * the {@link ObservableValue} which changed its value + * @param oldNode + * the old node + * @param newNode + * the new node + */ + private void updateNodeClipToChangingNode( + @SuppressWarnings("unused") ObservableValue<? extends Node> o, Node oldNode, Node newNode) { + + // resize the rectangle to match the new node + resizeRectangleToNodeBounds(nodeClip, newNode); + + // move the listener from one node to the next + if (oldNode != null) { + oldNode.boundsInParentProperty().removeListener(updateNodeClipToNewBoundsListener); + } + if (newNode != null) { + newNode.boundsInParentProperty().addListener(updateNodeClipToNewBoundsListener); + } + } + + /** + * Resizes the specified rectangle to the specified node's {@link Node#boundsInParentProperty() boundsInParent}. + * + * @param rectangle + * the {@link Rectangle} which will be resized + * @param node + * the {@link Node} to whose bounds the {@code rectangle} will be resized + */ + private static void resizeRectangleToNodeBounds(Rectangle rectangle, Node node) { + if (node == null) { + resizeRectangleToZero(rectangle); + } else { + resizeRectangleToBounds(rectangle, node.getBoundsInParent()); + } + } + + /** + * Resized the specified rectangle so that its upper left point is {@code (0, 0)} and its width and height are + * both 0. + * + * @param rectangle + * the {@link Rectangle} which will be resized + */ + private static void resizeRectangleToZero(Rectangle rectangle) { + rectangle.setX(0); + rectangle.setY(0); + rectangle.setWidth(0); + rectangle.setHeight(0); + } + + /** + * Resized the specified rectangle so that it matches the specified bounds, i.e. it will have the same upper + * left point and width and height. + * + * @param rectangle + * the {@link Rectangle} which will be resized + * @param bounds + * the {@link Bounds} to which the rectangle will be resized + */ + private static void resizeRectangleToBounds(Rectangle rectangle, Bounds bounds) { + rectangle.setX(bounds.getMinX()); + rectangle.setY(bounds.getMinY()); + rectangle.setWidth(bounds.getWidth()); + rectangle.setHeight(bounds.getHeight()); + } + + /** + * Sets the specified clip on the {@link #clippedNode}. + * + * @param clip + * the {@link Node} which is used as a clip + */ + private void setClip(Node clip) { + + /* + * UGLY WORKAROUND + * + * Setting the clip on the unselected area while it is invisible leads to either the clip having no effect + * or no area being displayed at all. Obviously I'm doing something wrong but I couldn't determine the root + * cause so I fixed the symptom. Now the area is turned visible, the clip is set and then it is made + * invisible again. + * + * Everything below but 'clippedNode.setClip(clip);' is part of that workaround. To reproduce the bug + * comment all those lines out. Then, after 'HelloSnapshotView' started, select 'NODE' for the unselected + * area boundary and draw a selection on the node. The area above the node which is not selected should be + * painted in a semi-opaque black but due to the bug it is not. Instead the area outside of the selection + * has no paint at all and is simply transparent. + * Note that if the boundary is turned back to CONTROL, a selection is made and then NODE is set again, the + * clips works properly and the preexisting selection's outer area is clipped to the node. + * + * If someone finds out what the *$#&? I've been doing wrong, please fix and be so kind to mail to + * nipa@codefx.org! :) + */ + + boolean workAroundVisibilityProblem = !clippedNode.isVisible(); + if (workAroundVisibilityProblem) { + clippedNode.visibleProperty().unbind(); + clippedNode.setVisible(true); + } + + clippedNode.setClip(clip); + + if (workAroundVisibilityProblem) { + rebindClippedNodeVisibility.run(); + } + } + + } + +} diff --git a/src/impl/org/controlsfx/skin/StatusBarSkin.java b/src/impl/org/controlsfx/skin/StatusBarSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..39a1b79e64f74634007b190566eadd52cd8782e7 --- /dev/null +++ b/src/impl/org/controlsfx/skin/StatusBarSkin.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; + +import org.controlsfx.control.StatusBar; + +public class StatusBarSkin extends SkinBase<StatusBar> { + + private HBox leftBox; + private HBox rightBox; + private Label label; + private ProgressBar progressBar; + + public StatusBarSkin(StatusBar statusBar) { + super(statusBar); + + leftBox = new HBox(); + leftBox.getStyleClass().add("left-items"); //$NON-NLS-1$ + + rightBox = new HBox(); + rightBox.getStyleClass().add("right-items"); //$NON-NLS-1$ + + progressBar = new ProgressBar(); + progressBar.progressProperty().bind(statusBar.progressProperty()); + progressBar.visibleProperty().bind( + Bindings.notEqual(0, statusBar.progressProperty())); + + label = new Label(); + label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + label.textProperty().bind(statusBar.textProperty()); + label.graphicProperty().bind(statusBar.graphicProperty()); + label.getStyleClass().add("status-label"); //$NON-NLS-1$ + + leftBox.getChildren().setAll(getSkinnable().getLeftItems()); + + rightBox.getChildren().setAll(getSkinnable().getRightItems()); + + statusBar.getLeftItems().addListener( + (Observable evt) -> leftBox.getChildren().setAll( + getSkinnable().getLeftItems())); + + statusBar.getRightItems().addListener( + (Observable evt) -> rightBox.getChildren().setAll( + getSkinnable().getRightItems())); + + GridPane gridPane = new GridPane(); + + GridPane.setFillHeight(leftBox, true); + GridPane.setFillHeight(rightBox, true); + GridPane.setFillHeight(label, true); + GridPane.setFillHeight(progressBar, true); + + GridPane.setVgrow(leftBox, Priority.ALWAYS); + GridPane.setVgrow(rightBox, Priority.ALWAYS); + GridPane.setVgrow(label, Priority.ALWAYS); + GridPane.setVgrow(progressBar, Priority.ALWAYS); + + GridPane.setHgrow(label, Priority.ALWAYS); + + gridPane.add(leftBox, 0, 0); + gridPane.add(label, 1, 0); + gridPane.add(progressBar, 2, 0); + gridPane.add(rightBox, 4, 0); + + getChildren().add(gridPane); + } +} diff --git a/src/impl/org/controlsfx/skin/TaskProgressViewSkin.java b/src/impl/org/controlsfx/skin/TaskProgressViewSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..6e6fba60df633761923387ad9b3260b89c561886 --- /dev/null +++ b/src/impl/org/controlsfx/skin/TaskProgressViewSkin.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.skin; + +import javafx.beans.binding.Bindings; +import javafx.concurrent.Task; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.SkinBase; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.util.Callback; + +import org.controlsfx.control.TaskProgressView; + +public class TaskProgressViewSkin<T extends Task<?>> extends + SkinBase<TaskProgressView<T>> { + + public TaskProgressViewSkin(TaskProgressView<T> monitor) { + super(monitor); + + BorderPane borderPane = new BorderPane(); + borderPane.getStyleClass().add("box"); + + // list view + ListView<T> listView = new ListView<>(); + listView.setPrefSize(500, 400); + listView.setPlaceholder(new Label("No tasks running")); + listView.setCellFactory(param -> new TaskCell()); + listView.setFocusTraversable(false); + + Bindings.bindContent(listView.getItems(), monitor.getTasks()); + borderPane.setCenter(listView); + + getChildren().add(listView); + } + + class TaskCell extends ListCell<T> { + private ProgressBar progressBar; + private Label titleText; + private Label messageText; + private Button cancelButton; + + private T task; + private BorderPane borderPane; + + public TaskCell() { + titleText = new Label(); + titleText.getStyleClass().add("task-title"); + + messageText = new Label(); + messageText.getStyleClass().add("task-message"); + + progressBar = new ProgressBar(); + progressBar.setMaxWidth(Double.MAX_VALUE); + progressBar.setMaxHeight(8); + progressBar.getStyleClass().add("task-progress-bar"); + + cancelButton = new Button("Cancel"); + cancelButton.getStyleClass().add("task-cancel-button"); + cancelButton.setTooltip(new Tooltip("Cancel Task")); + cancelButton.setOnAction(evt -> { + if (task != null) { + task.cancel(); + } + }); + + VBox vbox = new VBox(); + vbox.setSpacing(4); + vbox.getChildren().add(titleText); + vbox.getChildren().add(progressBar); + vbox.getChildren().add(messageText); + + BorderPane.setAlignment(cancelButton, Pos.CENTER); + BorderPane.setMargin(cancelButton, new Insets(0, 0, 0, 4)); + + borderPane = new BorderPane(); + borderPane.setCenter(vbox); + borderPane.setRight(cancelButton); + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + } + + @Override + public void updateIndex(int index) { + super.updateIndex(index); + + /* + * I have no idea why this is necessary but it won't work without + * it. Shouldn't the updateItem method be enough? + */ + if (index == -1) { + setGraphic(null); + getStyleClass().setAll("task-list-cell-empty"); + } + } + + @Override + protected void updateItem(T task, boolean empty) { + super.updateItem(task, empty); + + this.task = task; + + if (empty || task == null) { + getStyleClass().setAll("task-list-cell-empty"); + setGraphic(null); + } else if (task != null) { + getStyleClass().setAll("task-list-cell"); + progressBar.progressProperty().bind(task.progressProperty()); + titleText.textProperty().bind(task.titleProperty()); + messageText.textProperty().bind(task.messageProperty()); + cancelButton.disableProperty().bind( + Bindings.not(task.runningProperty())); + + Callback<T, Node> factory = getSkinnable().getGraphicFactory(); + if (factory != null) { + Node graphic = factory.call(task); + if (graphic != null) { + BorderPane.setAlignment(graphic, Pos.CENTER); + BorderPane.setMargin(graphic, new Insets(0, 4, 0, 0)); + borderPane.setLeft(graphic); + } + } else { + /* + * Really needed. The application might have used a graphic + * factory before and then disabled it. In this case the border + * pane might still have an old graphic in the left position. + */ + borderPane.setLeft(null); + } + + setGraphic(borderPane); + } + } + } +} diff --git a/src/impl/org/controlsfx/skin/ToggleSwitchSkin.java b/src/impl/org/controlsfx/skin/ToggleSwitchSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..e752032425f68bd4b89a2cd821b8c128d7d101aa --- /dev/null +++ b/src/impl/org/controlsfx/skin/ToggleSwitchSkin.java @@ -0,0 +1,242 @@ +/** + * Copyright (c) 2015, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package impl.org.controlsfx.skin; + +import com.sun.javafx.css.converters.SizeConverter; +import javafx.animation.TranslateTransition; +import javafx.beans.property.DoubleProperty; +import javafx.beans.value.WritableValue; +import javafx.css.CssMetaData; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableProperty; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; +import org.controlsfx.control.ToggleSwitch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Basic Skin implementation for the {@link ToggleSwitch} + */ +public class ToggleSwitchSkin extends SkinBase<ToggleSwitch> +{ + private final StackPane thumb; + private final StackPane thumbArea; + private final Label label; + private final StackPane labelContainer; + private final TranslateTransition transition; + + /** + * Constructor for all ToggleSwitchSkin instances. + * + * @param control The ToggleSwitch for which this Skin should attach to. + */ + public ToggleSwitchSkin(ToggleSwitch control) { + super(control); + + thumb = new StackPane(); + thumbArea = new StackPane(); + label = new Label(); + labelContainer = new StackPane(); + transition = new TranslateTransition(Duration.millis(getThumbMoveAnimationTime()), thumb); + + label.textProperty().bind(control.textProperty()); + getChildren().addAll(labelContainer, thumbArea, thumb); + labelContainer.getChildren().addAll(label); + StackPane.setAlignment(label, Pos.CENTER_LEFT); + + thumb.getStyleClass().setAll("thumb"); + thumbArea.getStyleClass().setAll("thumb-area"); + + thumbArea.setOnMouseReleased(event -> mousePressedOnToggleSwitch(control)); + thumb.setOnMouseReleased(event -> mousePressedOnToggleSwitch(control)); + control.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue.booleanValue() != oldValue.booleanValue()) + selectedStateChanged(); + }); + } + + private void selectedStateChanged() { + if(transition != null){ + transition.stop(); + } + + double thumbAreaWidth = snapSize(thumbArea.prefWidth(-1)); + double thumbWidth = snapSize(thumb.prefWidth(-1)); + double distance = thumbAreaWidth - thumbWidth; + /** + * If we are not selected, we need to go from right to left. + */ + if (!getSkinnable().isSelected()) { + thumb.setLayoutX(thumbArea.getLayoutX()); + transition.setFromX(distance); + transition.setToX(0); + } else { + thumb.setTranslateX(thumbArea.getLayoutX()); + transition.setFromX(0); + transition.setToX(distance); + } + transition.setCycleCount(1); + transition.play(); + } + + private void mousePressedOnToggleSwitch(ToggleSwitch toggleSwitch) { + toggleSwitch.setSelected(!toggleSwitch.isSelected()); + } + + + /** + * How many milliseconds it should take for the thumb to go from + * one edge to the other + */ + private DoubleProperty thumbMoveAnimationTime = null; + + private DoubleProperty thumbMoveAnimationTimeProperty() { + if (thumbMoveAnimationTime == null) { + thumbMoveAnimationTime = new StyleableDoubleProperty(200) { + + @Override + public Object getBean() { + return ToggleSwitchSkin.this; + } + + @Override + public String getName() { + return "thumbMoveAnimationTime"; + } + + @Override + public CssMetaData<ToggleSwitch,Number> getCssMetaData() { + return THUMB_MOVE_ANIMATION_TIME; + } + }; + } + return thumbMoveAnimationTime; + } + + private double getThumbMoveAnimationTime() { + return thumbMoveAnimationTime == null ? 200 : thumbMoveAnimationTime.get(); + } + + @Override + protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { + ToggleSwitch toggleSwitch = getSkinnable(); + double thumbWidth = snapSize(thumb.prefWidth(-1)); + double thumbHeight = snapSize(thumb.prefHeight(-1)); + thumb.resize(thumbWidth, thumbHeight); + //We must reset the TranslateX otherwise the thumb is mis-aligned when window is resized. + if (transition != null) { + transition.stop(); + } + thumb.setTranslateX(0); + + double thumbAreaY = snapPosition(contentY); + double thumbAreaWidth = snapSize(thumbArea.prefWidth(-1)); + double thumbAreaHeight = snapSize(thumbArea.prefHeight(-1)); + + thumbArea.resize(thumbAreaWidth, thumbAreaHeight); + thumbArea.setLayoutX(contentWidth - thumbAreaWidth); + thumbArea.setLayoutY(thumbAreaY); + + labelContainer.resize(contentWidth - thumbAreaWidth, thumbAreaHeight); + labelContainer.setLayoutY(thumbAreaY); + + if (!toggleSwitch.isSelected()) + thumb.setLayoutX(thumbArea.getLayoutX()); + else + thumb.setLayoutX(thumbArea.getLayoutX() + thumbAreaWidth - thumbWidth); + thumb.setLayoutY(thumbAreaY + (thumbAreaHeight - thumbHeight) / 2); + } + + + @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return leftInset + label.prefWidth(-1) + thumbArea.prefWidth(-1) + rightInset; + } + + @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return topInset + Math.max(thumb.prefHeight(-1), label.prefHeight(-1)) + bottomInset; + } + + @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + return leftInset + label.prefWidth(-1) + 20 + thumbArea.prefWidth(-1) + rightInset; + } + + @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return topInset + Math.max(thumb.prefHeight(-1), label.prefHeight(-1)) + bottomInset; + } + + private static final CssMetaData<ToggleSwitch, Number> THUMB_MOVE_ANIMATION_TIME = + new CssMetaData<ToggleSwitch, Number>("-thumb-move-animation-time", + SizeConverter.getInstance(), 200) { + + @Override + public boolean isSettable(ToggleSwitch toggleSwitch) { + final ToggleSwitchSkin skin = (ToggleSwitchSkin) toggleSwitch.getSkin(); + return skin.thumbMoveAnimationTime == null || + !skin.thumbMoveAnimationTime.isBound(); + } + + @Override + public StyleableProperty<Number> getStyleableProperty(ToggleSwitch toggleSwitch) { + final ToggleSwitchSkin skin = (ToggleSwitchSkin) toggleSwitch.getSkin(); + return (StyleableProperty<Number>) (WritableValue<Number>) skin.thumbMoveAnimationTimeProperty(); + } + }; + + private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; + + static { + final List<CssMetaData<? extends Styleable, ?>> styleables = + new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData()); + styleables.add(THUMB_MOVE_ANIMATION_TIME); + STYLEABLES = Collections.unmodifiableList(styleables); + } + + /** + * @return The CssMetaData associated with this class, which may include the + * CssMetaData of its super classes. + */ + public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { + return STYLEABLES; + } + + /** + * {@inheritDoc} + */ + @Override + public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { + return getClassCssMetaData(); + } +} + diff --git a/src/impl/org/controlsfx/spreadsheet/CellView.java b/src/impl/org/controlsfx/spreadsheet/CellView.java new file mode 100644 index 0000000000000000000000000000000000000000..591e1eba51bdbf92e7bbb801de568b78fdb63fba --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/CellView.java @@ -0,0 +1,659 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import java.util.Objects; +import java.util.Optional; +import java.util.logging.Logger; +import javafx.animation.FadeTransition; +import javafx.application.Platform; +import javafx.beans.binding.When; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.SetChangeListener; +import javafx.collections.WeakSetChangeListener; +import javafx.event.EventHandler; +import javafx.event.WeakEventHandler; +import javafx.scene.Node; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.Control; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableCell; +import javafx.scene.control.TablePositionBase; +import javafx.scene.control.TableView; +import javafx.scene.control.TableView.TableViewFocusModel; +import javafx.scene.control.TableView.TableViewSelectionModel; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.Region; +import javafx.util.Duration; +import org.controlsfx.control.spreadsheet.Grid; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetCellEditor; +import org.controlsfx.control.spreadsheet.SpreadsheetCellType; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * + * The View cell that will be visible on screen. It holds the + * {@link SpreadsheetCell}. + */ +public class CellView extends TableCell<ObservableList<SpreadsheetCell>, SpreadsheetCell> { + private final SpreadsheetHandle handle; + /** + * Because we don't want to recreate Tooltip each time the TableCell is + * re-used. We save it properly here so we avoid recreating it each time + * since it's really time-consuming. + */ + private Tooltip tooltip; + + /*************************************************************************** + * * Static Fields * * + **************************************************************************/ + private static final String ANCHOR_PROPERTY_KEY = "table.anchor"; //$NON-NLS-1$ + private static final int TOOLTIP_MAX_WIDTH = 400; + private static final Duration FADE_DURATION = Duration.millis(200); + + static TablePositionBase<?> getAnchor(Control table, TablePositionBase<?> focusedCell) { + return hasAnchor(table) ? (TablePositionBase<?>) table.getProperties().get(ANCHOR_PROPERTY_KEY) : focusedCell; + } + + static boolean hasAnchor(Control table) { + return table.getProperties().get(ANCHOR_PROPERTY_KEY) != null; + } + + static void setAnchor(Control table, TablePositionBase anchor) { + if (table != null && anchor == null) { + removeAnchor(table); + } else { + table.getProperties().put(ANCHOR_PROPERTY_KEY, anchor); + } + } + + static void removeAnchor(Control table) { + table.getProperties().remove(ANCHOR_PROPERTY_KEY); + } + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + public CellView(SpreadsheetHandle handle) { + this.handle = handle; + // When we detect a drag, we start the Full Drag so that other event + // will be fired + this.addEventHandler(MouseEvent.DRAG_DETECTED, new WeakEventHandler<>(startFullDragEventHandler)); + setOnMouseDragEntered(new WeakEventHandler<>(dragMouseEventHandler)); + + itemProperty().addListener(itemChangeListener); + + setOnMousePressed(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + getTableView().fireEvent(event); + } + }); + + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + @Override + public void startEdit() { + if (!isEditable()) { + getTableView().edit(-1, null); + return; + } + /** + * If this CellView has no parent, this means that it was stacked into + * the cellsMap of the GridRowSkin, but the weakRef was dropped. So this + * CellView is still reacting to events, but it's not part of the + * sceneGraph! So we must deactivate this cell and let the real Cell in + * the sceneGraph take the edition. + */ + if(getParent() == null){ + updateTableView(null); + updateTableRow(null); + updateTableColumn(null); + return; + } + final int column = this.getTableView().getColumns().indexOf(this.getTableColumn()); + final int row = getIndex(); + // We start to edit only if the Cell is a normal Cell (aka visible). + final SpreadsheetView spv = handle.getView(); + final Grid grid = spv.getGrid(); + final SpreadsheetView.SpanType type = grid.getSpanType(spv, row, column); + //FIXME with the reverse algorithm in virtualFlow, is this still necessary? + if (type == SpreadsheetView.SpanType.NORMAL_CELL || type == SpreadsheetView.SpanType.ROW_VISIBLE) { + + /** + * We may come to the situation where this method is called two + * times. One time by the row inside the VirtualFlow. And another by + * the row inside myFixedCells used by our GridVirtualFlow. + * + * In that case, we have to give priority to the one used by the + * VirtualFlow. So we just check if the row is managed. If not, we + * know for sure that the our GridVirtualFlow has stepped out. + */ + if (!getTableRow().isManaged()) { + return; + } + + GridCellEditor editor = getEditor(getItem(), spv); + if (editor != null) { + super.startEdit(); + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + editor.startEdit(); + }else{ + getTableView().edit(-1, null); + } + } + } + + @Override + public void commitEdit(SpreadsheetCell newValue) { + //When commiting, we bring the value smoothly. + FadeTransition fadeTransition = new FadeTransition(FADE_DURATION, this); + fadeTransition.setFromValue(0); + fadeTransition.setToValue(1); + fadeTransition.play(); + + if (!isEditing()) { + return; + } + super.commitEdit(newValue); + + setContentDisplay(ContentDisplay.LEFT); + updateItem(newValue, false); + + if (getTableView() != null) { + getTableView().requestFocus(); + } + } + + @Override + public void cancelEdit() { + if (!isEditing()) { + return; + } + + super.cancelEdit(); + + setContentDisplay(ContentDisplay.LEFT); + updateItem(getItem(), false); + + if (getTableView() != null) { + getTableView().requestFocus(); + } + } + + @Override + public void updateItem(final SpreadsheetCell item, boolean empty) { + final boolean emptyRow = getTableView().getItems().size() < getIndex() + 1; + /** + * don't call super.updateItem() because it will trigger cancelEdit() if + * the cell is being edited. It causes calling commitEdit() ALWAYS call + * cancelEdit as well which is undesired. + * + */ + if (!isEditing()) { + super.updateItem(item, empty && emptyRow); + } + if (empty && isSelected()) { + updateSelected(false); + } + if (empty && emptyRow) { + textProperty().unbind(); + setText(null); + // do not nullify graphic here. Let the TableRow to control cell + // dislay + // setGraphic(null); + setContentDisplay(null); + } else if (!isEditing() && item != null) { + show(item); + if (item.getGraphic() == null) { + setGraphic(null); + } + } + } + + /** + * Called in the gridRowSkinBase when doing layout This allow not to + * override opacity in the row and let the cell handle itself + * @param cell + */ + public void show(final SpreadsheetCell cell) { + // We reset the settings + textProperty().bind(cell.textProperty()); + setCellGraphic(cell); + + Optional<String> tooltipText = cell.getTooltip(); + String trimTooltip = tooltipText.isPresent() ? tooltipText.get().trim() : null; + + if (trimTooltip != null && !trimTooltip.isEmpty()) { + /** + * Here we check if the Tooltip has not been created in order NOT TO + * re-create it for nothing as it is a really time-consuming + * operation. + */ + Tooltip localTooltip = getAvailableTooltip(); + if (localTooltip != null) { + if (!Objects.equals(localTooltip.getText(), trimTooltip)) { + getTooltip().setText(trimTooltip); + } + } else { + /** + * Ensure that modification of ToolTip are set on the JFX thread + * because an exception can be thrown otherwise. + */ + getValue(() -> { + Tooltip newTooltip = new Tooltip(tooltipText.get()); + newTooltip.setWrapText(true); + newTooltip.setMaxWidth(TOOLTIP_MAX_WIDTH); + setTooltip(newTooltip); + } + ); + } + } else { + //We save that tooltip + if(getTooltip() != null){ + tooltip = getTooltip(); + } + setTooltip(null); + } + + setWrapText(cell.isWrapText()); + + setEditable(cell.isEditable()); + + if (cell.getCellType().acceptDrop()) { + setOnDragOver(new EventHandler<DragEvent>() { + + @Override + public void handle(DragEvent event) { + Dragboard db = event.getDragboard(); + if (db.hasFiles()) { + event.acceptTransferModes(TransferMode.ANY); + } else { + event.consume(); + } + } + }); + // Dropping over surface + setOnDragDropped(new EventHandler<DragEvent>() { + @Override + public void handle(DragEvent event) { + Dragboard db = event.getDragboard(); + boolean success = false; + if (db.hasFiles() && db.getFiles().size() == 1) { + if (getItem().getCellType().match(db.getFiles().get(0))) { + handle.getView().getGrid().setCellValue(getItem().getRow(), getItem().getColumn(), + getItem().getCellType().convertValue(db.getFiles().get(0))); + success = true; + } + } + event.setDropCompleted(success); + event.consume(); + } + }); + } else { + setOnDragOver(null); + setOnDragDropped(null); + } + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + /** + * See if a tootlip is available (either on the TableCell already, or in the + * Stack). And then set it to the TableCell. + * + * @return + */ + private Tooltip getAvailableTooltip(){ + if(getTooltip() != null){ + return getTooltip(); + } + if(tooltip != null){ + setTooltip(tooltip); + return tooltip; + } + return null; + } + + private void setCellGraphic(SpreadsheetCell item) { + + if (isEditing()) { + return; + } + Node graphic = item.getGraphic(); + if (graphic != null) { + /** + * This workaround is added for the first row containing a graphic + * because for an unknown reason, the graphic is translated to a + * negative value so it's not fully visible. So we add those + * listener that watch those changes, and try to get the previous + * value (the right one) if the new value goes out of bounds. + */ +// if (item.getRow() == 0) { +// graphic.layoutXProperty().removeListener(firstRowLayoutXListener); +// graphic.layoutXProperty().addListener(firstRowLayoutXListener); +// +// graphic.layoutYProperty().removeListener(firstRowLayoutYListener); +// graphic.layoutYProperty().addListener(firstRowLayoutYListener); +// } + + if (graphic instanceof ImageView) { + ImageView image = (ImageView) graphic; + image.setCache(true); + image.setPreserveRatio(true); + image.setSmooth(true); + if(image.getImage() != null){ + image.fitHeightProperty().bind( + new When(heightProperty().greaterThan(image.getImage().getHeight())).then( + image.getImage().getHeight()).otherwise(heightProperty())); + image.fitWidthProperty().bind( + new When(widthProperty().greaterThan(image.getImage().getWidth())).then( + image.getImage().getWidth()).otherwise(widthProperty())); + } + /** + * If we have a Region and no text, we force it to take full + * space. But we want to impact the minSize in order to let the + * prefSize to be computed if necessary. + */ + } else if (graphic instanceof Region && item.getItem() == null) { + Region region = (Region) graphic; + region.minHeightProperty().bind(heightProperty()); + region.minWidthProperty().bind(widthProperty()); + } + setGraphic(graphic); + /** + * In case of a resize of the column, we have new cells that steal + * the image from the original TableCell. So we check here if we are + * not in that case so that the Graphic of the SpreadsheetCell will + * always be on the latest tableView and therefore fully visible. + */ + if (!getChildren().contains(graphic)) { + getChildren().add(graphic); + } + } else { + setGraphic(null); + } + } + +// private final ChangeListener<Number> firstRowLayoutXListener = new ChangeListener<Number>() { +// @Override +// public void changed(ObservableValue<? extends Number> ov, Number oldLayoutX, Number newLayoutX) { +// if (getItem() != null && getItem().getGraphic() != null && newLayoutX.doubleValue() < 0 && oldLayoutX != null) { +// getItem().getGraphic().setLayoutX(oldLayoutX.doubleValue()); +// } +// } +// }; +// +// private final ChangeListener<Number> firstRowLayoutYListener = new ChangeListener<Number>() { +// @Override +// public void changed(ObservableValue<? extends Number> ov, Number oldLayoutY, Number newLayoutY) { +// if (getItem() != null && getItem().getGraphic() != null && newLayoutY.doubleValue() < 0 && oldLayoutY != null) { +// getItem().getGraphic().setLayoutY(oldLayoutY.doubleValue()); +// } +// } +// }; + + /** + * Return an instance of Editor specific to the Cell type We are not using + * the build-in editor-Cell because we cannot know in advance which editor + * we will need. Furthermore, we want to control the behavior very closely + * in regards of the spanned cell (invisible etc). + * + * @param cell + * The SpreadsheetCell + * @param bc + * The SpreadsheetCell + * @return + */ + private GridCellEditor getEditor(final SpreadsheetCell cell, final SpreadsheetView spv) { + SpreadsheetCellType<?> cellType = cell.getCellType(); + Optional<SpreadsheetCellEditor> cellEditor = spv.getEditor(cellType); + + if (cellEditor.isPresent()) { + GridCellEditor editor = handle.getCellsViewSkin().getSpreadsheetCellEditorImpl(); + /** + * Sometimes, we end up here with the editor already editing. But + * this case should not happen. If a cell is calling startEdit, + * this means we want to edit the cell and the editor should not be + * editing another cell. So we just cancel the edition and give the + * editor to the cell because we may not be able to edit anything. + */ + if (editor.isEditing()) { + if (editor.getModelCell() != null) { + StringBuilder builder = new StringBuilder(); + builder.append("The cell at row ").append(editor.getModelCell().getRow()) + .append(" and column ").append(editor.getModelCell().getColumn()) + .append(" was in edition and cell at row ").append(cell.getRow()) + .append(" and column ").append(cell.getColumn()) + .append(" requested edition. This situation should not happen as the previous cell should not be in edition."); + Logger.getLogger("root").warning(builder.toString()); + } + + editor.endEdit(false); + } + + editor.updateSpreadsheetCell(this); + editor.updateDataCell(cell); + editor.updateSpreadsheetCellEditor(cellEditor.get()); + return editor; + } else { + return null; + } + } + + private final ChangeListener<Node> graphicListener = new ChangeListener<Node>() { + @Override + public void changed(ObservableValue<? extends Node> arg0, Node arg1, Node newGraphic) { + setCellGraphic(getItem()); + } + }; + + private final WeakChangeListener<Node> weakGraphicListener = new WeakChangeListener<>(graphicListener); + + private final SetChangeListener<String> styleClassListener = new SetChangeListener<String>() { + @Override + public void onChanged(javafx.collections.SetChangeListener.Change<? extends String> arg0) { + if (arg0.wasAdded()) { + getStyleClass().add(arg0.getElementAdded()); + } else if (arg0.wasRemoved()) { + getStyleClass().remove(arg0.getElementRemoved()); + } + } + }; + + private final WeakSetChangeListener<String> weakStyleClassListener = new WeakSetChangeListener<>(styleClassListener); + + //Listeners for the styles, not initialized by default in order not to impact performance + private ChangeListener<String> styleListener; + private WeakChangeListener<String> weakStyleListener; + + /** + * Method that will select all the cells between the drag place and that + * cell. + * + * @param e + */ + private void dragSelect(MouseEvent e) { + // If the mouse event is not contained within this tableCell, then + // we don't want to react to it. + if (!this.contains(e.getX(), e.getY())) { + return; + } + final TableView<ObservableList<SpreadsheetCell>> tableView = getTableView(); + if (tableView == null) { + return; + } + + final int count = tableView.getItems().size(); + if (getIndex() >= count) { + return; + } + + final TableViewSelectionModel<ObservableList<SpreadsheetCell>> sm = tableView.getSelectionModel(); + if (sm == null) { + return; + } + + final int row = getIndex(); + final int column = tableView.getVisibleLeafIndex(getTableColumn()); + + // For spanned Cells + final SpreadsheetCell cell = (SpreadsheetCell) getItem(); + final int rowCell = cell.getRow() + cell.getRowSpan() - 1; + final int columnCell = cell.getColumn() + cell.getColumnSpan() - 1; + + final TableViewFocusModel<?> fm = tableView.getFocusModel(); + if (fm == null) { + return; + } + + final TablePositionBase<?> focusedCell = fm.getFocusedCell(); + final MouseButton button = e.getButton(); + if (button == MouseButton.PRIMARY) { + // we add all cells/rows between the current selection focus and + // this cell/row (inclusive) to the current selection. + final TablePositionBase<?> anchor = getAnchor(tableView, focusedCell); + + /** + * FIXME We need to clarify how we want to select the cells. If a + * spanned cell is in the way, which minRow/maxRow will be taken? + * Where the mouse is exactly? Or where the "motherCell" is? This + * needs some thinking. + */ + // and then determine all row and columns which must be selected + int minRow = Math.min(anchor.getRow(), row); + minRow = Math.min(minRow, rowCell); + int maxRow = Math.max(anchor.getRow(), row); + maxRow = Math.max(maxRow, rowCell); + int minColumn = Math.min(anchor.getColumn(), column); + minColumn = Math.min(minColumn, columnCell); + int maxColumn = Math.max(anchor.getColumn(), column); + maxColumn = Math.max(maxColumn, columnCell); + + // clear selection, but maintain the anchor + if (!e.isShortcutDown()) + sm.clearSelection(); + if (minColumn != -1 && maxColumn != -1) + sm.selectRange(minRow, tableView.getColumns().get(minColumn), maxRow, + tableView.getColumns().get(maxColumn)); + setAnchor(tableView, anchor); + } + + } + + /** + * Will safely execute the request on the JFX thread by checking whether we + * are on the JFX thread or not. + * + * @param runnable + */ + public static void getValue(final Runnable runnable) { + if (Platform.isFxApplicationThread()) { + runnable.run(); + } else { + Platform.runLater(runnable); + } + } + + @Override + protected javafx.scene.control.Skin<?> createDefaultSkin() { + return new CellViewSkin(this); + }; + + private final EventHandler<MouseEvent> startFullDragEventHandler = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent arg0) { + if (handle.getGridView().getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE)) { + setAnchor(getTableView(), getTableView().getFocusModel().getFocusedCell()); + startFullDrag(); + } + } + }; + + private final EventHandler<MouseEvent> dragMouseEventHandler = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent arg0) { + dragSelect(arg0); + } + }; + + private final ChangeListener<SpreadsheetCell> itemChangeListener = new ChangeListener<SpreadsheetCell>() { + + @Override + public void changed(ObservableValue<? extends SpreadsheetCell> arg0, SpreadsheetCell oldItem, + SpreadsheetCell newItem) { + if (oldItem != null) { + oldItem.getStyleClass().removeListener(weakStyleClassListener); + oldItem.graphicProperty().removeListener(weakGraphicListener); + + if(oldItem.styleProperty() != null){ + oldItem.styleProperty().removeListener(weakStyleListener); + } + } + if (newItem != null) { + getStyleClass().clear(); + getStyleClass().setAll(newItem.getStyleClass()); + + newItem.getStyleClass().addListener(weakStyleClassListener); + setCellGraphic(newItem); + newItem.graphicProperty().addListener(weakGraphicListener); + + if(newItem.styleProperty() != null){ + initStyleListener(); + newItem.styleProperty().addListener(weakStyleListener); + setStyle(newItem.getStyle()); + }else{ + //We clear the previous style. + setStyle(null); + } + } + } + }; + + private void initStyleListener(){ + if(styleListener == null){ + styleListener = (ObservableValue<? extends String> observable, String oldValue, String newValue) -> { + styleProperty().set(newValue); + }; + } + weakStyleListener = new WeakChangeListener<>(styleListener); + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/CellViewSkin.java b/src/impl/org/controlsfx/spreadsheet/CellViewSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..a41ff3df307e27eb680d743cccadf1d446f86ff1 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/CellViewSkin.java @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.scene.control.skin.TableCellSkin; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.WeakEventHandler; +import javafx.scene.Node; +import javafx.scene.control.TableCell; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Region; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetCell.CornerPosition; + +/** + * + * This is the skin for the {@link CellView}. + * + * Its main goal is to draw an object (a triangle) on cells which have their + * {@link SpreadsheetCell#commentedProperty()} set to true. + * + */ +public class CellViewSkin extends TableCellSkin<ObservableList<SpreadsheetCell>, SpreadsheetCell> { + + private final static String TOP_LEFT_CLASS = "top-left"; //$NON-NLS-1$ + private final static String TOP_RIGHT_CLASS = "top-right"; //$NON-NLS-1$ + private final static String BOTTOM_RIGHT_CLASS = "bottom-right"; //$NON-NLS-1$ + private final static String BOTTOM_LEFT_CLASS = "bottom-left"; //$NON-NLS-1$ + /** + * The size of the edge of the triangle FIXME Handling of static variable + * will be changed. + */ + private static final int TRIANGLE_SIZE = 8; + /** + * The region we will add on the cell when necessary. + */ + private Region topLeftRegion = null; + private Region topRightRegion = null; + private Region bottomRightRegion = null; + private Region bottomLeftRegion = null; + + public CellViewSkin(TableCell<ObservableList<SpreadsheetCell>, SpreadsheetCell> tableCell) { + super(tableCell); + tableCell.itemProperty().addListener(weakItemChangeListener); + if (tableCell.getItem() != null) { + tableCell.getItem().addEventHandler(SpreadsheetCell.CORNER_EVENT_TYPE, weakTriangleEventHandler); + } + } + + @Override + protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + /** + * If we have an Image in the Cell, its fitHeight will be affected by + * the cell height (see CellView). But during calculation for autofit + * option, we want to know the real prefHeight of this cell. Apparently, + * the fitHeight option is returned by default so we must override and + * return the Height of the image inside. + */ + Node graphic = getSkinnable().getGraphic(); + if (graphic != null && graphic instanceof ImageView) { + ImageView view = (ImageView) graphic; + if (view.getImage() != null) { + return view.getImage().getHeight(); + } + } + return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); + } + + @Override + protected void layoutChildren(double x, final double y, final double w, final double h) { + super.layoutChildren(x, y, w, h); + if (getSkinnable().getItem() != null) { + layoutTriangle(); + } + } + + private void layoutTriangle() { + SpreadsheetCell cell = getSkinnable().getItem(); + + handleTopLeft(cell); + handleTopRight(cell); + handleBottomLeft(cell); + handleBottomRight(cell); + + getSkinnable().requestLayout(); + } + + private void handleTopLeft(SpreadsheetCell cell) { + if (cell.isCornerActivated(CornerPosition.TOP_LEFT)) { + if (topLeftRegion == null) { + topLeftRegion = getRegion(CornerPosition.TOP_LEFT); + } + if (!getChildren().contains(topLeftRegion)) { + getChildren().add(topLeftRegion); + } + topLeftRegion.relocate(0, snappedTopInset() - 1); + } else if (topLeftRegion != null) { + getChildren().remove(topLeftRegion); + topLeftRegion = null; + } + } + + private void handleTopRight(SpreadsheetCell cell) { + if (cell.isCornerActivated(CornerPosition.TOP_RIGHT)) { + if (topRightRegion == null) { + topRightRegion = getRegion(CornerPosition.TOP_RIGHT); + } + if (!getChildren().contains(topRightRegion)) { + getChildren().add(topRightRegion); + } + topRightRegion.relocate(getSkinnable().getWidth() - TRIANGLE_SIZE, snappedTopInset() - 1); + } else if (topRightRegion != null) { + getChildren().remove(topRightRegion); + topRightRegion = null; + } + } + + private void handleBottomRight(SpreadsheetCell cell) { + if (cell.isCornerActivated(CornerPosition.BOTTOM_RIGHT)) { + if (bottomRightRegion == null) { + bottomRightRegion = getRegion(CornerPosition.BOTTOM_RIGHT); + } + if (!getChildren().contains(bottomRightRegion)) { + getChildren().add(bottomRightRegion); + } + bottomRightRegion.relocate(getSkinnable().getWidth() - TRIANGLE_SIZE, getSkinnable().getHeight() - TRIANGLE_SIZE); + } else if (bottomRightRegion != null) { + getChildren().remove(bottomRightRegion); + bottomRightRegion = null; + } + } + private void handleBottomLeft(SpreadsheetCell cell) { + if (cell.isCornerActivated(CornerPosition.BOTTOM_LEFT)) { + if (bottomLeftRegion == null) { + bottomLeftRegion = getRegion(CornerPosition.BOTTOM_LEFT); + } + if (!getChildren().contains(bottomLeftRegion)) { + getChildren().add(bottomLeftRegion); + } + bottomLeftRegion.relocate(0, getSkinnable().getHeight() - TRIANGLE_SIZE); + } else if (bottomLeftRegion != null) { + getChildren().remove(bottomLeftRegion); + bottomLeftRegion = null; + } + } + + private static Region getRegion(CornerPosition position) { + Region region = new Region(); + region.resize(TRIANGLE_SIZE, TRIANGLE_SIZE); + region.getStyleClass().add("cell-corner"); //$NON-NLS-1$ + switch (position) { + case TOP_LEFT: + region.getStyleClass().add(TOP_LEFT_CLASS); + break; + case TOP_RIGHT: + region.getStyleClass().add(TOP_RIGHT_CLASS); + break; + case BOTTOM_RIGHT: + region.getStyleClass().add(BOTTOM_RIGHT_CLASS); + break; + case BOTTOM_LEFT: + region.getStyleClass().add(BOTTOM_LEFT_CLASS); + break; + + } + + return region; + } + + private final EventHandler<Event> triangleEventHandler = new EventHandler<Event>() { + + @Override + public void handle(Event event) { + getSkinnable().requestLayout(); + } + }; + private final WeakEventHandler weakTriangleEventHandler = new WeakEventHandler(triangleEventHandler); + + private final ChangeListener<SpreadsheetCell> itemChangeListener = new ChangeListener<SpreadsheetCell>() { + @Override + public void changed(ObservableValue<? extends SpreadsheetCell> arg0, SpreadsheetCell oldCell, + SpreadsheetCell newCell) { + if (oldCell != null) { + oldCell.removeEventHandler(SpreadsheetCell.CORNER_EVENT_TYPE, weakTriangleEventHandler); + } + if (newCell != null) { + newCell.addEventHandler(SpreadsheetCell.CORNER_EVENT_TYPE, weakTriangleEventHandler); + } + if (getSkinnable().getItem() != null) { + layoutTriangle(); + } + } + }; + private final WeakChangeListener<SpreadsheetCell> weakItemChangeListener = new WeakChangeListener<>(itemChangeListener); +} diff --git a/src/impl/org/controlsfx/spreadsheet/FocusModelListener.java b/src/impl/org/controlsfx/spreadsheet/FocusModelListener.java new file mode 100644 index 0000000000000000000000000000000000000000..a359fb2dc0254b7feae0ed0f33736bc8a0ee0176 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/FocusModelListener.java @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2013, 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * + * The FocusModel Listener adapted to the SpreadsheetView regarding Span. + */ +public class FocusModelListener implements ChangeListener<TablePosition<ObservableList<SpreadsheetCell>, ?>> { + + private final TableView.TableViewFocusModel<ObservableList<SpreadsheetCell>> tfm; + private final SpreadsheetGridView cellsView; + private final SpreadsheetView spreadsheetView; + + /** + * Constructor. + * + * @param spreadsheetView + * @param cellsView + */ + public FocusModelListener(SpreadsheetView spreadsheetView, SpreadsheetGridView cellsView) { + tfm = cellsView.getFocusModel(); + this.spreadsheetView = spreadsheetView; + this.cellsView = cellsView; + } + + @Override + public void changed(ObservableValue<? extends TablePosition<ObservableList<SpreadsheetCell>, ?>> ov, + final TablePosition<ObservableList<SpreadsheetCell>, ?> oldPosition, + final TablePosition<ObservableList<SpreadsheetCell>, ?> newPosition) { + final SpreadsheetView.SpanType spanType = spreadsheetView.getSpanType(newPosition.getRow(), newPosition.getColumn()); + switch (spanType) { + case ROW_SPAN_INVISIBLE: + // If we notice that the new focused cell is the previous one, + // then it means that we were + // already on the cell and we wanted to go below. + if (!spreadsheetView.isPressed() && oldPosition.getColumn() == newPosition.getColumn() && oldPosition.getRow() == newPosition.getRow() - 1) { + Platform.runLater(() -> { + tfm.focus(getNextRowNumber(oldPosition, cellsView), oldPosition.getTableColumn()); + }); + + } else { + // If the current focused cell if hidden by row span, we go + // above + Platform.runLater(() -> { + tfm.focus(newPosition.getRow() - 1, newPosition.getTableColumn()); + }); + } + + break; + case BOTH_INVISIBLE: + // If the current focused cell if hidden by a both (row and + // column) span, we go left-above + Platform.runLater(() -> { + tfm.focus(newPosition.getRow() - 1, cellsView.getColumns().get(newPosition.getColumn() - 1)); + }); + break; + case COLUMN_SPAN_INVISIBLE: + // If we notice that the new focused cell is the previous one, + // then it means that we were + // already on the cell and we wanted to go right. + if (!spreadsheetView.isPressed() && oldPosition.getColumn() == newPosition.getColumn() - 1 && oldPosition.getRow() == newPosition.getRow()) { + + Platform.runLater(() -> { + tfm.focus(oldPosition.getRow(), getTableColumnSpan(oldPosition, cellsView)); + }); + } else { + // If the current focused cell if hidden by column span, we + // go left + + Platform.runLater(() -> { + tfm.focus(newPosition.getRow(), cellsView.getColumns().get(newPosition.getColumn() - 1)); + }); + } + default: + break; + } + } + + /** + * Return the TableColumn right after the current TablePosition (including + * the ColumSpan to be on a visible Cell) + * + * @param t the current TablePosition + * @return + */ + static TableColumn<ObservableList<SpreadsheetCell>, ?> getTableColumnSpan(final TablePosition<?, ?> t, SpreadsheetGridView cellsView) { + return cellsView.getVisibleLeafColumn(t.getColumn() + + cellsView.getItems().get(t.getRow()).get(t.getColumn()).getColumnSpan()); + } + + /** + * Return the Row number right after the current TablePosition (including + * the RowSpan to be on a visible Cell) + * + * @param pos + * @param cellsView + * @return + */ + public static int getNextRowNumber(final TablePosition<?, ?> pos, TableView<ObservableList<SpreadsheetCell>> cellsView) { + return cellsView.getItems().get(pos.getRow()).get(pos.getColumn()).getRowSpan() + + cellsView.getItems().get(pos.getRow()).get(pos.getColumn()).getRow(); + } + + public static int getPreviousRowNumber(final TablePosition<?, ?> pos, TableView<ObservableList<SpreadsheetCell>> cellsView) { + return cellsView.getItems().get(pos.getRow()).get(pos.getColumn()).getRow() -1; + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/GridCellEditor.java b/src/impl/org/controlsfx/spreadsheet/GridCellEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..bf5c01a92e6e467b110b06dd7861ee42c9db881c --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/GridCellEditor.java @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanExpression; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.control.TextArea; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; + +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetCellEditor; +import org.controlsfx.control.spreadsheet.SpreadsheetCellType; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +public class GridCellEditor { + + /*************************************************************************** + * * Protected/Private Fields * * + **************************************************************************/ + + private final SpreadsheetHandle handle; + // transient properties - these fields will change based on the current + // cell being edited. + private SpreadsheetCell modelCell; + private CellView viewCell; + private BooleanExpression focusProperty; + + private boolean editing = false; + + //The cell's editor + private SpreadsheetCellEditor spreadsheetCellEditor; + + //The last key pressed in order to select cell below if it was "enter" + private KeyCode lastKeyPressed; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + + /** + * Construct the GridCellEditor. + */ + public GridCellEditor(SpreadsheetHandle handle) { + this.handle = handle; + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + /** + * Update the internal {@link SpreadsheetCell}. + * + * @param cell + */ + public void updateDataCell(SpreadsheetCell cell) { + this.modelCell = cell; + } + + /** + * Update the internal {@link CellView} + * + * @param cell + */ + public void updateSpreadsheetCell(CellView cell) { + this.viewCell = cell; + } + + /** + * Update the SpreadsheetCellEditor + * + * @param spreadsheetCellEditor + */ + public void updateSpreadsheetCellEditor(final SpreadsheetCellEditor spreadsheetCellEditor) { + this.spreadsheetCellEditor = spreadsheetCellEditor; + } + + /** + * Whenever you want to stop the edition, you call that method.<br/> + * True means you're trying to commit the value, then + * {@link SpreadsheetCellType#match(java.lang.Object) } will be called + * in order to verify that the value is correct.<br/> + * + * False means you're trying to cancel the value and it will be follow by + * {@link #end()}.<br/> + * See SpreadsheetCellEditor description + * + * @param commitValue true means commit, false means cancel + */ + public void endEdit(boolean commitValue) { + if (commitValue && editing) { + final SpreadsheetView view = handle.getView(); + boolean match = modelCell.getCellType().match(spreadsheetCellEditor.getControlValue()); + + if (match && viewCell != null) { + Object value = modelCell.getCellType().convertValue(spreadsheetCellEditor.getControlValue()); + + // We update the value + view.getGrid().setCellValue(modelCell.getRow(), modelCell.getColumn(), value); + editing = false; + viewCell.commitEdit(modelCell); + end(); + spreadsheetCellEditor.end(); + + //We select the cell below if "enter" was typed. + if (KeyCode.ENTER.equals(lastKeyPressed)) { + handle.getView().getSelectionModel().clearAndSelectNextCell(); + } else if (KeyCode.TAB.equals(lastKeyPressed)) { + handle.getView().getSelectionModel().clearAndSelectRightCell(); + handle.getCellsViewSkin().scrollHorizontally(); + } + } + } + + if (editing) { + editing = false; + if(viewCell != null){ + viewCell.cancelEdit(); + } + end(); + if(spreadsheetCellEditor != null){ + spreadsheetCellEditor.end(); + } + } + } + + /** + * Return if this editor is currently being used. + * + * @return if this editor is being used. + */ + public boolean isEditing() { + return editing; + } + + public SpreadsheetCell getModelCell() { + return modelCell; + } + + /*************************************************************************** + * * Protected/Private Methods * * + **************************************************************************/ + void startEdit() { + //If we do not reset this, it could false the endEdit behavior in case no key was pressed. + lastKeyPressed = null; + editing = true; + + handle.getGridView().addEventFilter(KeyEvent.KEY_PRESSED, enterKeyPressed); + + handle.getCellsViewSkin().getVBar().valueProperty().addListener(endEditionListener); + handle.getCellsViewSkin().getHBar().valueProperty().addListener(endEditionListener); + + Control editor = spreadsheetCellEditor.getEditor(); + + // Then we call the user editor in order for it to be ready + Object value = modelCell.getItem(); + //We don't want the editor to go beyond the cell boundaries + Double maxHeight = Math.min(viewCell.getHeight(), spreadsheetCellEditor.getMaxHeight()); + + if (editor != null) { + viewCell.setGraphic(editor); + editor.setMaxHeight(maxHeight); + editor.setPrefWidth(viewCell.getWidth()); + } + + spreadsheetCellEditor.startEdit(value); + + if (editor != null) { + focusProperty = getFocusProperty(editor); + focusProperty.addListener(focusListener); + } + + } + + private void end() { + if(focusProperty != null){ + focusProperty.removeListener(focusListener); + focusProperty = null; + } + handle.getCellsViewSkin().getVBar().valueProperty().removeListener(endEditionListener); + handle.getCellsViewSkin().getHBar().valueProperty().removeListener(endEditionListener); + + handle.getGridView().removeEventFilter(KeyEvent.KEY_PRESSED, enterKeyPressed); + + this.modelCell = null; + this.viewCell = null; + } + + /** + * If we have a TextArea, we need to return a custom BooleanExpression + * because we want to let the editor in place even if the user is touching + * the scrollBars inside the textArea. + * + * @param control + * @return + */ + private BooleanExpression getFocusProperty(Control control) { + if (control instanceof TextArea) { + return Bindings.createBooleanBinding(() -> { + if(handle.getView().getScene() == null){ + return false; + } + for (Node n = handle.getView().getScene().getFocusOwner(); n != null; n = n.getParent()) { + if (n == control) { + return true; + } + } + return false; + }, handle.getView().getScene().focusOwnerProperty()); + } else { + return control.focusedProperty(); + } + } + + /** + * When we stop editing a cell, if enter was pressed, we want to go to the next line. + */ + private final EventHandler<KeyEvent> enterKeyPressed = new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + lastKeyPressed = t.getCode(); + } + }; + + private final ChangeListener<Boolean> focusListener = new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean isFocus) { + if (!isFocus) { + endEdit(true); + } + } + }; + + private final InvalidationListener endEditionListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + endEdit(true); + } + }; +} diff --git a/src/impl/org/controlsfx/spreadsheet/GridRow.java b/src/impl/org/controlsfx/spreadsheet/GridRow.java new file mode 100644 index 0000000000000000000000000000000000000000..bbffc04c5bdd81e7700c85174c386649bdf355da --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/GridRow.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2013, 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.WeakEventHandler; +import javafx.scene.control.Skin; +import javafx.scene.control.TableRow; +import javafx.scene.input.MouseEvent; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + + +/** + * + * The tableRow which will holds the SpreadsheetCell. + */ +public class GridRow extends TableRow<ObservableList<SpreadsheetCell>> { + + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final SpreadsheetHandle handle; + /** + * When the row is fixed, it may have a shift from its original position + * which we need in order to layout the cells properly and also for the + * rectangle selection. + */ + DoubleProperty verticalShift = new SimpleDoubleProperty(); + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + public GridRow(SpreadsheetHandle handle) { + super(); + this.handle = handle; + + /** + * FIXME Bug? When re-using the row, it should re-compute the prefHeight and not + * keep the old value. + */ + this.indexProperty().addListener(weakPrefHeightListener); + this.visibleProperty().addListener(weakPrefHeightListener); + + handle.getView().gridProperty().addListener(weakPrefHeightListener); + + /** + * When the height is changing elsewhere, we need to update ourself if necessary. + */ + handle.getCellsViewSkin().rowHeightMap.addListener(new MapChangeListener<Integer, Double>() { + + @Override + public void onChanged(MapChangeListener.Change<? extends Integer, ? extends Double> change) { + if (change.wasAdded() && change.getKey() == getIndex()) { + setRowHeight(change.getValueAdded()); + } else if (change.wasRemoved() && change.getKey() == getIndex()) { + setRowHeight(computePrefHeight(-1)); + } + } + }); + /** + * When we are adding deported cells (fixed in columns) into a row via + * addCell. The cell is not receiving the DRAG_DETECTED eventHandler + * because it's the row that receives it first. If it's the case, we + * must give the event to the cell underneath. + */ + this.addEventHandler(MouseEvent.DRAG_DETECTED, weakDragHandler); + } + /*************************************************************************** + * * Protected Methods * * + **************************************************************************/ + + void addCell(CellView cell) { + getChildren().add(cell); + } + + void removeCell(CellView gc) { + getChildren().remove(gc); + } + + SpreadsheetView getSpreadsheetView() { + return handle.getView(); + } + + @Override + protected double computePrefHeight(double width) { + return handle.getCellsViewSkin().getRowHeight(getIndex()); + } + + @Override + protected double computeMinHeight(double width) { + return handle.getCellsViewSkin().getRowHeight(getIndex()); + } + + @Override + protected Skin<?> createDefaultSkin() { + return new GridRowSkin(handle, this); + } + + private final InvalidationListener setPrefHeightListener = new InvalidationListener() { + + @Override + public void invalidated(Observable o) { + setRowHeight(computePrefHeight(-1)); + } + }; + + private final WeakInvalidationListener weakPrefHeightListener = new WeakInvalidationListener(setPrefHeightListener); + + public void setRowHeight(double height) { + CellView.getValue(() -> { + setHeight(height); + }); + + setPrefHeight(height); + handle.getCellsViewSkin().rectangleSelection.updateRectangle(); + } + + private final EventHandler<MouseEvent> dragDetectedEventHandler = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + if (event.getTarget().getClass().equals(GridRow.class) && event.getPickResult().getIntersectedNode() != null) { + Event.fireEvent(event.getPickResult().getIntersectedNode(), event); + } + } + }; + + private final WeakEventHandler<MouseEvent> weakDragHandler = new WeakEventHandler(dragDetectedEventHandler); +} diff --git a/src/impl/org/controlsfx/spreadsheet/GridRowSkin.java b/src/impl/org/controlsfx/spreadsheet/GridRowSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..a0bd3d17ffe64e6780889a1ac9b0499b70ab9c57 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/GridRowSkin.java @@ -0,0 +1,615 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.scene.control.behavior.CellBehaviorBase; +import com.sun.javafx.scene.control.behavior.TableRowBehavior; +import com.sun.javafx.scene.control.skin.CellSkinBase; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TableRow; +import org.controlsfx.control.spreadsheet.Grid; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetColumn; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +public class GridRowSkin extends CellSkinBase<TableRow<ObservableList<SpreadsheetCell>>, CellBehaviorBase<TableRow<ObservableList<SpreadsheetCell>>>> { + + private final SpreadsheetHandle handle; + private final SpreadsheetView spreadsheetView; + + private Reference<HashMap<TableColumnBase, CellView>> cellsMap; + + private final List<CellView> cells = new ArrayList<>(); + + public GridRowSkin(SpreadsheetHandle handle, TableRow<ObservableList<SpreadsheetCell>> gridRow) { + super(gridRow, new TableRowBehavior<>(gridRow)); + this.handle = handle; + spreadsheetView = handle.getView(); + + getSkinnable().setPickOnBounds(false); + + registerChangeListener(gridRow.itemProperty(), "ITEM"); + registerChangeListener(gridRow.indexProperty(), "INDEX"); + } + + @Override + protected void handleControlPropertyChanged(String p) { + super.handleControlPropertyChanged(p); + + if ("INDEX".equals(p)) { + // Fix for RT-36661, where empty table cells were showing content, as they + // had incorrect table cell indices (but the table row index was correct). + // Note that we only do the update on empty cells to avoid the issue + // noted below in requestCellUpdate(). + if (getSkinnable().isEmpty()) { + requestCellUpdate(); + } + } else if ("ITEM".equals(p)) { + requestCellUpdate(); + } else if ("FIXED_CELL_SIZE".equals(p)) { +// fixedCellSize = fixedCellSizeProperty().get(); +// fixedCellSizeEnabled = fixedCellSize > 0; + } + } + + private void requestCellUpdate() { + getSkinnable().requestLayout(); + + // update the index of all children cells (RT-29849). + // Note that we do this after the TableRow item has been updated, + // rather than when the TableRow index has changed (as this will be + // before the row has updated its item). This will result in the + // issue highlighted in RT-33602, where the table cell had the correct + // item whilst the row had the old item. + final int newIndex = getSkinnable().getIndex(); + /** + * When the index is changing, we need to clear out all the children + * because we may end up with useless cell in the row. + */ + getChildren().clear(); + for (int i = 0, max = cells.size(); i < max; i++) { + cells.get(i).updateIndex(newIndex); + } + } + + @Override + protected void layoutChildren(double x, final double y, final double w, final double h) { + + final ObservableList<? extends TableColumnBase<?, ?>> visibleLeafColumns = handle.getGridView().getVisibleLeafColumns(); + if (visibleLeafColumns.isEmpty()) { + super.layoutChildren(x, y, w, h); + return; + } + + final GridRow control = (GridRow) getSkinnable(); + final SpreadsheetGridView gridView = (SpreadsheetGridView) handle.getGridView(); + final Grid grid = spreadsheetView.getGrid(); + final int index = control.getIndex(); + + /** + * If this row is out of bounds, this means that the row is displayed + * either at the top or at the bottom. In any case, this row is not + * meant to be seen so we clear its children list in order not to show + * previous TableCell that could be there. + */ + if (index < 0 || index >= gridView.getItems().size()) { + getChildren().clear(); + putCellsInCache(); + return; + } + + final List<SpreadsheetCell> row = grid.getRows().get(index); + final List<SpreadsheetColumn> columns = spreadsheetView.getColumns(); + final ObservableList<TableColumn<ObservableList<SpreadsheetCell>, ?>> tableViewColumns = gridView.getColumns(); + /** + * If we use "setGrid" on SpreadsheetView, we must be careful because we + * set our columns after (due to threading safety). So if, by mistake, + * we are in layout and the columns are set in SpreadsheetView, but not + * in TableView (yet). Then just return and wait for next calling. + */ + if (columns.size() != tableViewColumns.size()) { + return; + } + + getSkinnable().setVisible(true); + // layout the individual column cells + double width; + double height; + + final double verticalPadding = snappedTopInset() + snappedBottomInset(); + final double horizontalPadding = snappedLeftInset() + + snappedRightInset(); + /** + * Here we make the distinction between the official controlHeight and + * the customHeight that we may apply. + */ + double controlHeight = getTableRowHeight(index); + double customHeight = controlHeight == Grid.AUTOFIT ? GridViewSkin.DEFAULT_CELL_HEIGHT : controlHeight; + + final GridViewSkin skin = handle.getCellsViewSkin(); + skin.hBarValue.set(index, true); + + // determine the width of the visible portion of the table + double headerWidth = gridView.getWidth(); + final double hbarValue = skin.getHBar().getValue(); + + /** + * FOR FIXED ROWS + */ + ((GridRow) getSkinnable()).verticalShift.setValue(getFixedRowShift(index)); + + double fixedColumnWidth = 0; + List<CellView> fixedCells = new ArrayList(); + + //We compute the cells here + putCellsInCache(); + + boolean firstVisibleCell = false; + CellView lastCell = null; + boolean needToBeShifted; + boolean rowHeightChange = false; + for (int indexColumn = 0; indexColumn < columns.size(); indexColumn++) { + + width = snapSize(columns.get(indexColumn).getWidth()) - snapSize(horizontalPadding); + + final SpreadsheetCell spreadsheetCell = row.get(indexColumn); + boolean isVisible = !isInvisible(x, width, hbarValue, headerWidth, spreadsheetCell.getColumnSpan()); + + if (columns.get(indexColumn).isFixed()) { + isVisible = true; + } + + if (!isVisible) { + if (firstVisibleCell) { + break; + } + x += width; + continue; + } + final CellView tableCell = getCell(gridView.getColumns().get(indexColumn)); + + cells.add(0, tableCell); + + // In case the node was treated previously + tableCell.setManaged(true); + + /** + * FOR FIXED COLUMNS + */ + double tableCellX = 0; + + /** + * We need to update the fixedColumnWidth only on visible cell and + * we need to add the full width including the span. + * + * If we fail to do so, we may be in the situation where x will grow + * with the correct width and not fixedColumnWidth. Thus some cell + * that should be shifted will not because the computation based on + * fixedColumnWidth will be wrong. + */ + boolean increaseFixedWidth = false; + //Virtualization of column + // We translate that column by the Hbar Value if it's fixed + if (columns.get(indexColumn).isFixed()) { + if (hbarValue + fixedColumnWidth > x && spreadsheetCell.getColumn() == indexColumn) { + increaseFixedWidth = true; + tableCellX = Math.abs(hbarValue - x + fixedColumnWidth); +// tableCell.toFront(); + fixedColumnWidth += width; +// isVisible = true; // If in fixedColumn, it's obviously visible + fixedCells.add(tableCell); + } + } + + if (isVisible) { + final SpreadsheetView.SpanType spanType = grid.getSpanType(spreadsheetView, index, indexColumn); + + switch (spanType) { + case ROW_SPAN_INVISIBLE: + case BOTH_INVISIBLE: + fixedCells.remove(tableCell); + getChildren().remove(tableCell); +// cells.remove(tableCell); + x += width; + continue; // we don't want to fall through + case COLUMN_SPAN_INVISIBLE: + fixedCells.remove(tableCell); + getChildren().remove(tableCell); +// cells.remove(tableCell); + continue; // we don't want to fall through + case ROW_VISIBLE: +// final TableViewSpanSelectionModel sm = (TableViewSpanSelectionModel) handle.getGridView().getSelectionModel(); +// final TableColumn<ObservableList<SpreadsheetCell>, ?> col = tableViewColumns.get(indexColumn); + + /** + * In case this cell was selected before but we scroll + * up/down and it's invisible now. It has to pass his + * "selected property" to the new Cell in charge of + * spanning + */ +// final TablePosition<ObservableList<SpreadsheetCell>, ?> selectedPosition = sm.isSelectedRange(index, col, indexColumn); + // If the selected cell is in the same row, no need to re-select it +// if (selectedPosition != null +// //When shift selecting, all cells become ROW_VISIBLE so +// //We avoid loop selecting here +// && skin.containsRow(index) +// && selectedPosition.getRow() != index) { +// sm.clearSelection(selectedPosition.getRow(), +// selectedPosition.getTableColumn()); +// sm.select(index, col); +// } + case NORMAL_CELL: // fall through and carry on + if (tableCell.getIndex() != index) { + tableCell.updateIndex(index); + } else { + tableCell.updateItem(spreadsheetCell, false); + } + /** + * Here we need to add the cells on the first position + * because this row may contain some deported cells from + * other rows in order to be on top in term of z-order. + * So the cell we're currently adding must not recover + * them. + */ + if (tableCell.getParent() == null) { + getChildren().add(0, tableCell); + } + } + + if (spreadsheetCell.getColumnSpan() > 1) { + /** + * we need to span multiple columns, so we sum up the width + * of the additional columns, adding it to the width + * variable + */ + for (int i = 1, colSpan = spreadsheetCell.getColumnSpan(), max1 = columns + .size() - indexColumn; i < colSpan && i < max1; i++) { + double tempWidth = snapSize(columns.get(indexColumn + i).getWidth()); + width += tempWidth; + if (increaseFixedWidth) { + fixedColumnWidth += tempWidth; + } + } + } + + /** + * If we are in autofit and the prefHeight of this cell is + * superior to the default cell height. Then we will use this + * new height for row's height. + * + * We then need to apply the value to previous cell, and also + * layout the children because since we are layouting upward, + * next rows needs to know that this row is bigger than usual. + */ + if (controlHeight == Grid.AUTOFIT && !tableCell.isEditing()) { + double tempHeight = tableCell.prefHeight(width); + if (tempHeight > customHeight) { + rowHeightChange = true; + skin.rowHeightMap.put(index, tempHeight); + for (CellView cell : cells) { + /** + * We need to add the difference between the + * previous height and the new height. If we were + * just setting the new height, the row spanning + * cell would be shorter. That's why we need to use + * the cell height. + */ + cell.resize(cell.getWidth(), cell.getHeight() + (tempHeight - customHeight)); + } + customHeight = tempHeight; + skin.getFlow().layoutChildren(); + } + } + + height = customHeight; + height = snapSize(height) - snapSize(verticalPadding); + /** + * We need to span multiple rows, so we sum up the height of all + * the rows. The height of the current row is ignored and the + * whole value is computed. + */ + if (spreadsheetCell.getRowSpan() > 1) { + height = 0; + final int maxRow = spreadsheetCell.getRow() + spreadsheetCell.getRowSpan(); + for (int i = spreadsheetCell.getRow(); i < maxRow; ++i) { + height += snapSize(skin.getRowHeight(i)); + } + } + + //Fix for JDK-8146406 + needToBeShifted = false; + /** + * If the current cell has no left border, and the previous cell + * had no right border, and we're fixed. We may have the problem + * where there is a tiny gap between the cells when scrolling + * horizontally. Thus we must enlarge this cell a bit, and shift + * it a bit in order to mask that gap. If the cell has a border + * defined, the problem seems not to happen. + */ + if (spreadsheetView.getFixedRows().contains(index) + && lastCell != null + && !hasRightBorder(lastCell) + && !hasLeftBorder(tableCell)) { + tableCell.resize(width +1, height); + needToBeShifted = true; + } else { + tableCell.resize(width, height); + } + lastCell = tableCell; + // We want to place the layout always at the starting cell. + double spaceBetweenTopAndMe = 0; + for (int p = spreadsheetCell.getRow(); p < index; ++p) { + spaceBetweenTopAndMe += skin.getRowHeight(p); + } + + tableCell.relocate(x + tableCellX + (needToBeShifted? -1 : 0), snappedTopInset() + - spaceBetweenTopAndMe + ((GridRow) getSkinnable()).verticalShift.get()); + + // Request layout is here as (partial) fix for RT-28684 +// tableCell.requestLayout(); + } else { + getChildren().remove(tableCell); + } + x += width; + } + skin.fixedColumnWidth = fixedColumnWidth; + handleFixedCell(fixedCells, index); + removeUselessCell(index); + if (handle.getCellsViewSkin().lastRowLayout.get() == true) { + handle.getCellsViewSkin().lastRowLayout.setValue(false); + } + /** + * If we modified an height here, ROW_HEIGHT_CHANGE will not be + * triggered, because it's not the user who has modified that. So the + * rectangle will not update, we need to force it here. + */ + if (rowHeightChange && spreadsheetView.getFixedRows().contains(index)) { + skin.computeFixedRowHeight(); + } + } + + private boolean hasRightBorder(CellView tableCell) { + return tableCell.getBorder() != null + && !tableCell.getBorder().isEmpty() + && tableCell.getBorder().getStrokes().get(0).getWidths().getRight() > 0; + } + + private boolean hasLeftBorder(CellView tableCell) { + return tableCell.getBorder() != null + && !tableCell.getBorder().isEmpty() + && tableCell.getBorder().getStrokes().get(0).getWidths().getLeft()> 0; + } + + /** + * Here we want to remove of the sceneGraph cells that are not used. + * + * Before we were removing the cells that we were getting from the cache. + * But that is not enough because some cells can be added somehow, and stay + * within the row. Since we do not often clear the children because of some + * deportedCell present inside, we must use that Predicate to clear all + * CellView not contained in cells and with the same index. Thus we preserve + * the deported cell. + */ + private void removeUselessCell(int index) { + getChildren().removeIf((Node t) -> { + if (t instanceof CellView) { + return !cells.contains(t) && ((CellView) t).getIndex() == index; + } + return false; + }); + } + + /** + * This handles the fixed cells in column. + * + * @param fixedCells + * @param index + */ + private void handleFixedCell(List<CellView> fixedCells, int index) { + if (fixedCells.isEmpty()) { + return; + } + + /** + * If we have a fixedCell (in column) and that cell may be recovered by + * a rowSpan, we want to put that tableCell ahead in term of z-order. So + * we need to put it in another row. + */ + if (handle.getCellsViewSkin().rowToLayout.get(index)) { + GridRow gridRow = handle.getCellsViewSkin().getFlow().getTopRow(); + if (gridRow != null) { + for (CellView cell : fixedCells) { + final double originalLayoutY = getSkinnable().getLayoutY() + cell.getLayoutY(); + gridRow.removeCell(cell); + gridRow.addCell(cell); + if (handle.getCellsViewSkin().deportedCells.containsKey(gridRow)) { + handle.getCellsViewSkin().deportedCells.get(gridRow).add(cell); + } else { + Set<CellView> temp = new HashSet<>(); + temp.add(cell); + handle.getCellsViewSkin().deportedCells.put(gridRow, temp); + } + /** + * I need to have the layoutY of the original row, but also + * to remove the layoutY of the row I'm adding in. Because + * if the first row is fixed and is undergoing a bit of + * translate in order to be visible, we need to remove that + * "bit of translate". + */ + cell.relocate(cell.getLayoutX(), originalLayoutY - gridRow.getLayoutY()); + } + } + } else { + for (CellView cell : fixedCells) { + cell.toFront(); + } + } + } + + /** + * Return the Cache. Here we use a WeakReference because the WeakHashMap is + * not working. TableCell added to it are not removed if the GC wants them. + * So we put the whole cache in WeakReference. In normal condition, the + * cache is not trashed that much and is efficient. In the case where the + * user scroll horizontally a lot, that cache can then be trashed in order + * to avoid OutOfMemoryError. + * + * @return + */ + private HashMap<TableColumnBase, CellView> getCellsMap() { + if (cellsMap == null || cellsMap.get() == null) { + HashMap<TableColumnBase, CellView> map = new HashMap<>(); + cellsMap = new WeakReference<>(map); + return map; + } + return cellsMap.get(); + } + + /** + * This will put all current displayed cell into the cache. + */ + private void putCellsInCache() { + for (CellView cell : cells) { + getCellsMap().put(cell.getTableColumn(), cell); + } + cells.clear(); + } + + /** + * This will retrieve a cell for the specified column. If the cell exists in + * the cache, it's extracted from it. Otherwise, a cell is created. + * + * @param tcb + * @return + */ + private CellView getCell(TableColumnBase tcb) { + TableColumn tableColumn = (TableColumn<CellView, ?>) tcb; + CellView cell; + if (getCellsMap().containsKey(tableColumn)) { + return getCellsMap().remove(tableColumn); + } else { + cell = (CellView) tableColumn.getCellFactory().call(tableColumn); + cell.updateTableColumn(tableColumn); + cell.updateTableView(tableColumn.getTableView()); + cell.updateTableRow(getSkinnable()); + } + return cell; + } + + /** + * Return the space we need to shift that row if it's fixed. Also update the {@link GridViewSkin#getCurrentlyFixedRow() + * } . + * + * @param index + * @return + */ + private double getFixedRowShift(int index) { + double tableCellY = 0; + int positionY = spreadsheetView.getFixedRows().indexOf(index); + + //FIXME Integrate if fixedCellSize is enabled + //Computing how much space we need to translate + //because each row has different space. + double space = 0; + for (int o = 0; o < positionY; ++o) { + space += handle.getCellsViewSkin().getRowHeight(spreadsheetView.getFixedRows().get(o)); + } + + //If true, this row is fixed + if (positionY != -1 && getSkinnable().getLocalToParentTransform().getTy() <= space) { + //This row is a bit hidden on top so we translate then for it to be fully visible + tableCellY = space - getSkinnable().getLocalToParentTransform().getTy(); + handle.getCellsViewSkin().getCurrentlyFixedRow().add(index); + } else { + handle.getCellsViewSkin().getCurrentlyFixedRow().remove(index); + } + return tableCellY; + } + + /** + * Return the height of a row. + * + * @param row + * @return + */ + private double getTableRowHeight(int row) { + Double rowHeightCache = handle.getCellsViewSkin().rowHeightMap.get(row); + return rowHeightCache == null ? handle.getView().getGrid().getRowHeight(row) : rowHeightCache; + } + + /** + * Return true if the current cell is part of the sceneGraph. + * + * @param x beginning of the cell + * @param width total width of the cell + * @param hbarValue + * @param headerWidth width of the visible portion of the tableView + * @param columnSpan + * @return + */ + private boolean isInvisible(double x, double width, double hbarValue, + double headerWidth, int columnSpan) { + return (x + width < hbarValue && columnSpan == 1) || (x > hbarValue + headerWidth); + } + + @Override + protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { + double prefWidth = 0.0; + + final List<? extends TableColumnBase/*<T,?>*/> visibleLeafColumns = handle.getGridView().getVisibleLeafColumns(); + for (int i = 0, max = visibleLeafColumns.size(); i < max; i++) { + prefWidth += visibleLeafColumns.get(i).getWidth(); + } + + return prefWidth; + } + + @Override + protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().getPrefHeight(); + } + + @Override + protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return getSkinnable().getPrefHeight(); + } + + @Override + protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { + return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset); + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/GridViewBehavior.java b/src/impl/org/controlsfx/spreadsheet/GridViewBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..9ef5a8395f8cee5d61fae39956869c5bcf0b8a24 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/GridViewBehavior.java @@ -0,0 +1,509 @@ +/** + * Copyright (c) 2015 ControlsFX All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. * Neither the name of ControlsFX, any associated + * website, nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.scene.control.behavior.TableViewBehavior; +import javafx.collections.ObservableList; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TableFocusModel; +import javafx.scene.control.TablePositionBase; +import javafx.scene.control.TableSelectionModel; +import javafx.scene.control.TableView; +import javafx.util.Pair; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; + +/** + * + * This overrides {@link TableViewBehavior} in order to modify the selection + * behavior. The selection will basically work like Excel: + * + * Selection will always be rectangles. So selection by SHIFT will produce a + * rectangle extending your selection. + * + * Pressing SHORTCUT with an arrow will on a cell will do: + * + * - If the cell is empty, we go to the next (in the direction) non empty cell. + * + * - If the cell is not empty, then we either go to the last non empty cell if + * the next is not empty, or the first non empty cell if the next is empty. + * + * This is meant to increase navigation on non empty cell in a Spreadsheet more + * easily. + * + * Pressing SHORTCUT and SHIFT together will behave as the ShortCut previously + * explained but the selection will be extended instead of just selecting the + * new cell. + * + */ +public class GridViewBehavior extends TableViewBehavior<ObservableList<SpreadsheetCell>> { + + private GridViewSkin skin; + + public GridViewBehavior(TableView<ObservableList<SpreadsheetCell>> control) { + super(control); + } + + void setGridViewSkin(GridViewSkin skin) { + this.skin = skin; + } + + @Override + protected void updateCellVerticalSelection(int delta, Runnable defaultAction) { + TableViewSpanSelectionModel sm = (TableViewSpanSelectionModel) getSelectionModel(); + if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) { + return; + } + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + + final TablePositionBase focusedCell = getFocusedCell(); + + if (isShiftDown && getAnchor() != null) { + + final SpreadsheetCell cell = getControl().getItems().get(fm.getFocusedIndex()).get(focusedCell.getColumn()); + sm.direction = new Pair<>(delta, 0); + /** + * If the delta is >0, it means we want to go down, so we need to + * target the cell that is after our cell. So we need to take the + * last row of our cell if spanning. If the delta is < 0, it means + * we want to go up, so we just take the row. + */ + int newRow; + if (delta < 0) { + newRow = cell.getRow() + delta; + } else { + newRow = cell.getRow() + cell.getRowSpan() - 1 + delta; + } + + // we don't let the newRow go outside the bounds of the data + newRow = Math.max(Math.min(getItemCount() - 1, newRow), 0); + + final TablePositionBase<?> anchor = getAnchor(); + int minRow = Math.min(anchor.getRow(), newRow); + int maxRow = Math.max(anchor.getRow(), newRow); + int minColumn = Math.min(anchor.getColumn(), focusedCell.getColumn()); + int maxColumn = Math.max(anchor.getColumn(), focusedCell.getColumn()); + + sm.clearSelection(); + if (minColumn != -1 && maxColumn != -1) { + sm.selectRange(minRow, getControl().getColumns().get(minColumn), maxRow, + getControl().getColumns().get(maxColumn)); + } + fm.focus(newRow, focusedCell.getTableColumn()); + } else { + final int focusIndex = fm.getFocusedIndex(); + if (!sm.isSelected(focusIndex, focusedCell.getTableColumn())) { + sm.select(focusIndex, focusedCell.getTableColumn()); + } + defaultAction.run(); + } + } + + @Override + protected void updateCellHorizontalSelection(int delta, Runnable defaultAction) { + TableViewSpanSelectionModel sm = (TableViewSpanSelectionModel) getSelectionModel(); + if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) { + return; + } + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + + final TablePositionBase focusedCell = getFocusedCell(); + if (focusedCell == null || focusedCell.getTableColumn() == null) { + return; + } + + TableColumnBase adjacentColumn = getColumn(focusedCell.getTableColumn(), delta); + if (adjacentColumn == null) { + return; + } + + final int focusedCellRow = focusedCell.getRow(); + + if (isShiftDown && getAnchor() != null) { + final int columnPos = getVisibleLeafIndex(focusedCell.getTableColumn()); + + final SpreadsheetCell cell = getControl().getItems().get(focusedCellRow).get(columnPos); + + sm.direction = new Pair<>(0, delta); + final int newColumn;// = columnCell + delta; + if (delta < 0) { + newColumn = cell.getColumn() + delta; + } else { + newColumn = cell.getColumn() + cell.getColumnSpan() - 1 + delta; + } + final TablePositionBase<?> anchor = getAnchor(); + int minRow = Math.min(anchor.getRow(), focusedCellRow); + int maxRow = Math.max(anchor.getRow(), focusedCellRow); + int minColumn = Math.min(anchor.getColumn(), newColumn); + int maxColumn = Math.max(anchor.getColumn(), newColumn); + + sm.clearSelection(); + if (minColumn != -1 && maxColumn != -1) { + sm.selectRange(minRow, getControl().getColumns().get(minColumn), maxRow, + getControl().getColumns().get(maxColumn)); + } + fm.focus(focusedCell.getRow(), getColumn(newColumn)); + } else { + defaultAction.run(); + } + + } + + @Override + protected void focusPreviousRow() { + focusVertical(true); + } + + @Override + protected void focusNextRow() { + focusVertical(false); + } + + @Override + protected void focusLeftCell() { + focusHorizontal(true); + } + + @Override + protected void focusRightCell() { + focusHorizontal(false); + } + + @Override + protected void discontinuousSelectPreviousRow() { + discontinuousSelectVertical(true); + } + + @Override + protected void discontinuousSelectNextRow() { + discontinuousSelectVertical(false); + } + + @Override + protected void discontinuousSelectPreviousColumn() { + discontinuousSelectHorizontal(true); + } + + @Override + protected void discontinuousSelectNextColumn() { + discontinuousSelectHorizontal(false); + } + + private void focusVertical(boolean previous) { + TableSelectionModel sm = getSelectionModel(); + if (sm == null || sm.getSelectionMode() == SelectionMode.SINGLE) { + return; + } + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + + final TablePositionBase focusedCell = getFocusedCell(); + if (focusedCell == null || focusedCell.getTableColumn() == null) { + return; + } + + final SpreadsheetCell cell = getControl().getItems().get(focusedCell.getRow()).get(focusedCell.getColumn()); + sm.clearAndSelect(previous ? findPreviousRow(focusedCell, cell) : findNextRow(focusedCell, cell), focusedCell.getTableColumn()); + skin.focusScroll(); + } + + private void focusHorizontal(boolean previous) { + TableSelectionModel sm = getSelectionModel(); + if (sm == null) { + return; + } + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + final TablePositionBase focusedCell = getFocusedCell(); + if (focusedCell == null || focusedCell.getTableColumn() == null) { + return; + } + + final SpreadsheetCell cell = getControl().getItems().get(focusedCell.getRow()).get(focusedCell.getColumn()); + + sm.clearAndSelect(focusedCell.getRow(), getControl().getColumns().get(previous ? findPreviousColumn(focusedCell, cell) : findNextColumn(focusedCell, cell))); + skin.focusScroll(); + } + + private int findPreviousRow(TablePositionBase focusedCell, SpreadsheetCell cell) { + final ObservableList<ObservableList<SpreadsheetCell>> items = getControl().getItems(); + SpreadsheetCell temp; + //If my cell is empty, I seek the next non-empty + if (isEmpty(cell)) { + for (int row = focusedCell.getRow() - 1; row >= 0; --row) { + temp = items.get(row).get(focusedCell.getColumn()); + if (!isEmpty(temp)) { + return row; + } + } + } else if (focusedCell.getRow() - 1 >= 0 && !isEmpty(items.get(focusedCell.getRow() - 1).get(focusedCell.getColumn()))) { + for (int row = focusedCell.getRow() - 2; row >= 0; --row) { + temp = items.get(row).get(focusedCell.getColumn()); + if (isEmpty(temp)) { + return row + 1; + } + } + } else { + //If I'm not empty and the next is empty, I seek the first non empty + for (int row = focusedCell.getRow() - 2; row >= 0; --row) { + temp = items.get(row).get(focusedCell.getColumn()); + if (!isEmpty(temp)) { + return row; + } + } + } + + //If we're here, we then select the last on + return 0; + } + + + @Override + protected void selectCell(int rowDiff, int columnDiff) { + TableViewSpanSelectionModel sm = (TableViewSpanSelectionModel) getSelectionModel(); + if (sm == null) { + return; + } + sm.direction = new Pair<>(rowDiff, columnDiff); + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + + TablePositionBase focusedCell = getFocusedCell(); + int currentRow = focusedCell.getRow(); + int currentColumn = getVisibleLeafIndex(focusedCell.getTableColumn()); + + if (rowDiff < 0 && currentRow <= 0) return; + else if (rowDiff > 0 && currentRow >= getItemCount() - 1) return; + else if (columnDiff < 0 && currentColumn <= 0) return; + else if (columnDiff > 0 && currentColumn >= getVisibleLeafColumns().size() - 1) return; + else if (columnDiff > 0 && currentColumn == -1) return; + + TableColumnBase tc = focusedCell.getTableColumn(); + tc = getColumn(tc, columnDiff); + + int row = focusedCell.getRow() + rowDiff; + + sm.clearAndSelect(row, tc); + setAnchor(row, tc); + } + + private int findNextRow(TablePositionBase focusedCell, SpreadsheetCell cell) { + final ObservableList<ObservableList<SpreadsheetCell>> items = getControl().getItems(); + final int itemCount = getItemCount(); + SpreadsheetCell temp; + //If my cell is empty, I seek the next non-empty + if (isEmpty(cell)) { + for (int row = focusedCell.getRow() + 1; row < itemCount; ++row) { + temp = items.get(row).get(focusedCell.getColumn()); + if (!isEmpty(temp)) { + return row; + } + } + } else if (focusedCell.getRow() + 1 < itemCount && !isEmpty(items.get(focusedCell.getRow() + 1).get(focusedCell.getColumn()))) { + for (int row = focusedCell.getRow() + 2; row < getItemCount(); ++row) { + temp = items.get(row).get(focusedCell.getColumn()); + if (isEmpty(temp)) { + return row - 1; + } + } + } else { + for (int row = focusedCell.getRow() + 2; row < itemCount; ++row) { + temp = items.get(row).get(focusedCell.getColumn()); + if (!isEmpty(temp)) { + return row; + } + } + } + return itemCount - 1; + } + + private void discontinuousSelectVertical(boolean previous) { + TableSelectionModel sm = getSelectionModel(); + if (sm == null) { + return; + } + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + final TablePositionBase focusedCell = getFocusedCell(); + if (focusedCell == null || focusedCell.getTableColumn() == null) { + return; + } + + final SpreadsheetCell cell = getControl().getItems().get(fm.getFocusedIndex()).get(focusedCell.getColumn()); + + /** + * If the delta is >0, it means we want to go down, so we need to target + * the cell that is after our cell. So we need to take the last row of + * our cell if spanning. If the delta is < 0, it means we want to go up, + * so we just take the row. + */ + int newRow = previous ? findPreviousRow(focusedCell, cell) : findNextRow(focusedCell, cell);; + + // we don't let the newRow go outside the bounds of the data + newRow = Math.max(Math.min(getItemCount() - 1, newRow), 0); + + final TablePositionBase<?> anchor = getAnchor(); + int minRow = Math.min(anchor.getRow(), newRow); + int maxRow = Math.max(anchor.getRow(), newRow); + int minColumn = Math.min(anchor.getColumn(), focusedCell.getColumn()); + int maxColumn = Math.max(anchor.getColumn(), focusedCell.getColumn()); + + sm.clearSelection(); + if (minColumn != -1 && maxColumn != -1) { + sm.selectRange(minRow, getControl().getColumns().get(minColumn), maxRow, + getControl().getColumns().get(maxColumn)); + } + fm.focus(newRow, focusedCell.getTableColumn()); + skin.focusScroll(); + } + + private void discontinuousSelectHorizontal(boolean previous) { + TableSelectionModel sm = getSelectionModel(); + if (sm == null) { + return; + } + + TableFocusModel fm = getFocusModel(); + if (fm == null) { + return; + } + final TablePositionBase focusedCell = getFocusedCell(); + if (focusedCell == null || focusedCell.getTableColumn() == null) { + return; + } + + final int columnPos = getVisibleLeafIndex(focusedCell.getTableColumn()); + int focusedCellRow = focusedCell.getRow(); + final SpreadsheetCell cell = getControl().getItems().get(focusedCellRow).get(columnPos); + + final int newColumn = previous ? findPreviousColumn(focusedCell, cell) : findNextColumn(focusedCell, cell); + + final TablePositionBase<?> anchor = getAnchor(); + int minRow = Math.min(anchor.getRow(), focusedCellRow); + int maxRow = Math.max(anchor.getRow(), focusedCellRow); + int minColumn = Math.min(anchor.getColumn(), newColumn); + int maxColumn = Math.max(anchor.getColumn(), newColumn); + + sm.clearSelection(); + if (minColumn != -1 && maxColumn != -1) { + sm.selectRange(minRow, getControl().getColumns().get(minColumn), maxRow, + getControl().getColumns().get(maxColumn)); + } + fm.focus(focusedCell.getRow(), getColumn(newColumn)); + skin.focusScroll(); + } + + private int findNextColumn(TablePositionBase focusedCell, SpreadsheetCell cell) { + final ObservableList<ObservableList<SpreadsheetCell>> items = getControl().getItems(); + final int itemCount = getControl().getColumns().size(); + SpreadsheetCell temp; + //If my cell is empty, I seek the next non-empty + if (isEmpty(cell)) { + for (int column = focusedCell.getColumn() + 1; column < itemCount; ++column) { + temp = items.get(focusedCell.getRow()).get(column); + if (!isEmpty(temp)) { + return column; + } + } + } else if (focusedCell.getColumn() + 1 < itemCount && !isEmpty(items.get(focusedCell.getRow()).get(focusedCell.getColumn() + 1))) { + for (int column = focusedCell.getColumn() + 2; column < itemCount; ++column) { + temp = items.get(focusedCell.getRow()).get(column); + if (isEmpty(temp)) { + return column - 1; + } + } + } else { + for (int column = focusedCell.getColumn() + 2; column < itemCount; ++column) { + temp = items.get(focusedCell.getRow()).get(column); + if (!isEmpty(temp)) { + return column; + } + } + } + return itemCount - 1; + } + + private int findPreviousColumn(TablePositionBase focusedCell, SpreadsheetCell cell) { + final ObservableList<ObservableList<SpreadsheetCell>> items = getControl().getItems(); + SpreadsheetCell temp; + //If my cell is empty, I seek the next non-empty + if (isEmpty(cell)) { + for (int column = focusedCell.getColumn() - 1; column >= 0; --column) { + temp = items.get(focusedCell.getRow()).get(column); + if (!isEmpty(temp)) { + return column; + } + } + } else if (focusedCell.getColumn() - 1 >= 0 && !isEmpty(items.get(focusedCell.getRow()).get(focusedCell.getColumn() - 1))) { + for (int column = focusedCell.getColumn() - 2; column >= 0; --column) { + temp = items.get(focusedCell.getRow()).get(column); + if (isEmpty(temp)) { + return column + 1; + } + } + } else { + for (int column = focusedCell.getColumn() - 2; column >= 0; --column) { + temp = items.get(focusedCell.getRow()).get(column); + if (!isEmpty(temp)) { + return column; + } + } + } + return 0; + } + + /** + * Cell is empty if there's nothing in it or if we have a NaN instead of a + * proper Double. + * + * @param cell + * @return + */ + private boolean isEmpty(SpreadsheetCell cell) { + return cell.getGraphic() == null && (cell.getItem() == null + || (cell.getItem() instanceof Double && ((Double) cell.getItem()).isNaN())); + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/GridViewSkin.java b/src/impl/org/controlsfx/spreadsheet/GridViewSkin.java new file mode 100644 index 0000000000000000000000000000000000000000..afb42b31ef850068f17cd23c988fbc5fdf26a151 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/GridViewSkin.java @@ -0,0 +1,1138 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.ResizeFeaturesBase; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TableFocusModel; +import javafx.scene.control.TablePositionBase; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableSelectionModel; +import javafx.scene.control.TableView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import javafx.stage.Screen; +import javafx.util.Callback; + +import org.controlsfx.control.spreadsheet.Grid; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetColumn; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +import com.sun.javafx.scene.control.behavior.TableViewBehavior; +import com.sun.javafx.scene.control.skin.TableHeaderRow; +import com.sun.javafx.scene.control.skin.TableViewSkinBase; +import com.sun.javafx.scene.control.skin.VirtualFlow; +import javafx.application.Platform; +import javafx.event.Event; +import javafx.scene.control.ScrollBar; + +/** + * This skin is actually the skin of the SpreadsheetGridView (tableView) + * contained within the SpreadsheetView. The skin for the SpreadsheetView itself + * currently resides inside the SpreadsheetView constructor! + * + * We need to extends directly from TableViewSkinBase in order to work-around + * https://javafx-jira.kenai.com/browse/RT-34753 if we want to set a custom + * TableViewBehavior. + * + */ +public class GridViewSkin extends TableViewSkinBase<ObservableList<SpreadsheetCell>,ObservableList<SpreadsheetCell>,TableView<ObservableList<SpreadsheetCell>>,TableViewBehavior<ObservableList<SpreadsheetCell>>,TableRow<ObservableList<SpreadsheetCell>>,TableColumn<ObservableList<SpreadsheetCell>,?>> { + + /*************************************************************************** + * * STATIC FIELDS * * + **************************************************************************/ + + /** Default height of a row. */ + public static final double DEFAULT_CELL_HEIGHT; + + // FIXME This should seriously be investigated .. + private static final double DATE_CELL_MIN_WIDTH = 200 - Screen.getPrimary().getDpi(); + + static { + double cell_size = 24.0; + try { + Class<?> clazz = com.sun.javafx.scene.control.skin.CellSkinBase.class; + Field f = clazz.getDeclaredField("DEFAULT_CELL_SIZE"); //$NON-NLS-1$ + f.setAccessible(true); + cell_size = f.getDouble(null); + } catch (NoSuchFieldException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + DEFAULT_CELL_HEIGHT = cell_size; + } + + /** + * When we add some tableCell to some topRow in order for them to be on top + * in term of z-order. We may end up with the situation where the row that + * put the cell is not in the ViewPort anymore. For example when a fixedRow + * has taken over the real row when scrolling down. Then, the tableCell + * added is still hanging out in the topRow. That tableCell has no clue that + * its "creator" has been destroyed or re-used since that tableCell was not + * technically belonging to its "creator". Therefore, we need to track those + * cells in order to remove them each time. + */ + final Map<GridRow,Set<CellView>> deportedCells = new HashMap<>(); + /*************************************************************************** + * * PRIVATE FIELDS * * + **************************************************************************/ + /** + * When resizing, we save the height here in order to override default row + * height. package protected. + */ + ObservableMap<Integer, Double> rowHeightMap = FXCollections.observableHashMap(); + + /** The editor. */ + private GridCellEditor gridCellEditor; + + protected final SpreadsheetHandle handle; + protected SpreadsheetView spreadsheetView; + protected VerticalHeader verticalHeader; + protected HorizontalPicker horizontalPickers; + + /** + * The currently fixedRow. This handles an Integer's set of rows being + * fixed. NOT Fixable but truly fixed. + */ + private ObservableSet<Integer> currentlyFixedRow = FXCollections.observableSet(new HashSet<Integer>()); + + /** + * A list of Integer with the current selected Rows. This is useful for + * HorizontalHeader and VerticalHeader because they need to highlight when a + * selection is made. + */ + private final ObservableList<Integer> selectedRows = FXCollections.observableArrayList(); + + /** + * A list of Integer with the current selected Columns. This is useful for + * HorizontalHeader and VerticalHeader because they need to highlight when a + * selection is made. + */ + private final ObservableList<Integer> selectedColumns = FXCollections.observableArrayList(); + + /** + * The total height of the currently fixedRows. + */ + private double fixedRowHeight = 0; + + /** + * These variable try to optimize the layout of the rows in order not to layout + * every time every row. + * + * So rowToLayout contains the rows that really needs layout(contain span or fixed). + * + * And hBarValue is an indicator for the VirtualFlow. When the Hbar is touched, this BitSet + * is set to false. And when a row is drawing, it flips its value in this BitSet. + * So that we know when scrolling up or down whether a row has taken into account + * that the HBar was moved (otherwise, blank area may appear). + */ + BitSet hBarValue; + BitSet rowToLayout; + + /** + * This rectangle will be used for drawing a border around the selection. + */ + RectangleSelection rectangleSelection; + + /** + * This is the current width used by the currently fixed column on the left. + */ + double fixedColumnWidth; + + /** + * When we try to select cells after a setGrid, we end up with the cell + * selected but no visual confirmation. In order to prevent that, we need to + * warn the selectionModel when the layout is starting and then the + * selectionModel will do the appropriate actions in order to force the + * visual to come. + */ + BooleanProperty lastRowLayout = new SimpleBooleanProperty(true); + + /*************************************************************************** + * * CONSTRUCTOR * * + **************************************************************************/ + public GridViewSkin(final SpreadsheetHandle handle) { + super(handle.getGridView(), new GridViewBehavior(handle.getGridView())); + super.init(handle.getGridView()); + + this.handle = handle; + this.spreadsheetView = handle.getView(); + gridCellEditor = new GridCellEditor(handle); + TableView<ObservableList<SpreadsheetCell>> tableView = handle.getGridView(); + + //Set a new row factory, useful when handling row height. + tableView.setRowFactory(new Callback<TableView<ObservableList<SpreadsheetCell>>, TableRow<ObservableList<SpreadsheetCell>>>() { + @Override + public TableRow<ObservableList<SpreadsheetCell>> call(TableView<ObservableList<SpreadsheetCell>> p) { + return new GridRow(handle); + } + }); + + tableView.getStyleClass().add("cell-spreadsheet"); //$NON-NLS-1$ + + getCurrentlyFixedRow().addListener(currentlyFixedRowListener); + spreadsheetView.getFixedRows().addListener(fixedRowsListener); + spreadsheetView.getFixedColumns().addListener(fixedColumnsListener); + + init(); + /** + * When we are changing the grid we re-instantiate the rowToLayout because + * spans and fixedRow may have changed. + */ + handle.getView().gridProperty().addListener(new ChangeListener<Grid>() { + + @Override + public void changed(ObservableValue<? extends Grid> ov, Grid t, Grid t1) { + rowToLayout = initRowToLayoutBitSet(); + } + }); + hBarValue = new BitSet(handle.getView().getGrid().getRowCount()); + rowToLayout = initRowToLayoutBitSet(); + // Because fixedRow Listener is not reacting first time. + computeFixedRowHeight(); + + + EventHandler<MouseEvent> ml = (MouseEvent event) -> { + // RT-15127: cancel editing on scroll. This is a bit extreme + // (we are cancelling editing on touching the scrollbars). + // This can be improved at a later date. + if (tableView.getEditingCell() != null) { + tableView.edit(-1, null); + } + + // This ensures that the table maintains the focus, even when the vbar + // and hbar controls inside the flow are clicked. Without this, the + // focus border will not be shown when the user interacts with the + // scrollbars, and more importantly, keyboard navigation won't be + // available to the user. + tableView.requestFocus(); + }; + + getFlow().getVerticalBar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); + getFlow().getHorizontalBar().addEventFilter(MouseEvent.MOUSE_PRESSED, ml); + + // init the behavior 'closures' + TableViewBehavior<ObservableList<SpreadsheetCell>> behavior = getBehavior(); + behavior.setOnFocusPreviousRow(new Runnable() { + @Override public void run() { onFocusPreviousCell(); } + }); + behavior.setOnFocusNextRow(new Runnable() { + @Override public void run() { onFocusNextCell(); } + }); + behavior.setOnMoveToFirstCell(new Runnable() { + @Override public void run() { onMoveToFirstCell(); } + }); + behavior.setOnMoveToLastCell(new Runnable() { + @Override public void run() { onMoveToLastCell(); } + }); + behavior.setOnScrollPageDown(new Callback<Boolean, Integer>() { + @Override public Integer call(Boolean isFocusDriven) { return onScrollPageDown(isFocusDriven); } + }); + behavior.setOnScrollPageUp(new Callback<Boolean, Integer>() { + @Override public Integer call(Boolean isFocusDriven) { return onScrollPageUp(isFocusDriven); } + }); + behavior.setOnSelectPreviousRow(new Runnable() { + @Override public void run() { onSelectPreviousCell(); } + }); + behavior.setOnSelectNextRow(new Runnable() { + @Override public void run() { onSelectNextCell(); } + }); + behavior.setOnSelectLeftCell(new Runnable() { + @Override public void run() { onSelectLeftCell(); } + }); + behavior.setOnSelectRightCell(new Runnable() { + @Override public void run() { onSelectRightCell(); } + }); + + registerChangeListener(tableView.fixedCellSizeProperty(), "FIXED_CELL_SIZE"); + + } + + /** + * Compute the height of a particular row. If the row is in + * {@link Grid#AUTOFIT}, {@link #DEFAULT_CELL_HEIGHT} is returned. + * + * @param row + * @return + */ + public double getRowHeight(int row) { + Double rowHeightCache = rowHeightMap.get(row); + if (rowHeightCache == null) { + double rowHeight = handle.getView().getGrid().getRowHeight(row); + return rowHeight == Grid.AUTOFIT ? DEFAULT_CELL_HEIGHT : rowHeight; + } else { + return rowHeightCache; + } + } + + public double getFixedRowHeight() { + return fixedRowHeight; + } + + public ObservableList<Integer> getSelectedRows() { + return selectedRows; + } + + public ObservableList<Integer> getSelectedColumns() { + return selectedColumns; + } + + public GridCellEditor getSpreadsheetCellEditorImpl() { + return gridCellEditor; + } + + /** + * This return the GridRow which has the specified index if found. Otherwise + * null is returned. + * + * @param index + * @return + */ + public GridRow getRowIndexed(int index) { + List<? extends IndexedCell> cells = getFlow().getCells(); + if (!cells.isEmpty()) { + IndexedCell cell = cells.get(0); + if (index >= cell.getIndex() && index - cell.getIndex() < cells.size()) { + return (GridRow) cells.get(index - cell.getIndex()); + } + } + for (IndexedCell cell : getFlow().getFixedCells()) { + if (cell.getIndex() == index) { + return (GridRow) cell; + } + } + return null; + } + + /** + * This return the row at the specified index in the list. The index + * specified HAS NOTHING to do with the index of the row. + * @see #getRowIndexed(int) for a getting a row with its real index. + * @param index + * @return + */ + public GridRow getRow(int index) { + return (GridRow) getFlow().getCells().get(index); + } + + /** + * Indicate whether or not the row at the specified index is currently being + * displayed. + * + * @param index + * @return + */ + public final boolean containsRow(int index) { + for (Object obj : getFlow().getCells()) { + if (((GridRow) obj).getIndex() == index) + return true; + } + return false; + } + + public int getCellsSize() { + return getFlow().getCells().size(); + } + + public ScrollBar getHBar() { + if (getFlow() != null) { + return getFlow().getHorizontalBar(); + } + return null; + } + + public ScrollBar getVBar() { + return getFlow().getVerticalBar(); + } + + /** + * Will compute for every row the necessary height and fit the line. + * This can degrade performance a lot so need to use it wisely. + * But I don't see other solutions right now. + */ + public void resizeRowsToFitContent() { + Grid grid = spreadsheetView.getGrid(); + int maxRows = handle.getView().getGrid().getRowCount(); + for (int row = 0; row < maxRows; row++) { + if (grid.isRowResizable(row)) { + resizeRowToFitContent(row); + } + } + } + + /** + * Will compute for the row the necessary height and fit the line. + * This can degrade performance a lot so need to use it wisely. + * But I don't see other solutions right now. + * @param row + */ + public void resizeRowToFitContent(int row) { + if(getSkinnable().getColumns().isEmpty()){ + return; + } + final TableColumn<ObservableList<SpreadsheetCell>, ?> col = getSkinnable().getColumns().get(0); + List<?> items = itemsProperty().get(); + if (items == null || items.isEmpty()) { + return; + } + + if (!spreadsheetView.getGrid().isRowResizable(row)) { + return; + } + Callback/* <TableColumn<T, ?>, TableCell<T,?>> */ cellFactory = col.getCellFactory(); + if (cellFactory == null) { + return; + } + + CellView cell = (CellView) cellFactory.call(col); + if (cell == null) { + return; + } + + // set this property to tell the TableCell we want to know its actual + // preferred width, not the width of the associated TableColumnBase + cell.getProperties().put("deferToParentPrefWidth", Boolean.TRUE); //$NON-NLS-1$ + + // determine cell padding + double padding = 5; + + Node n = cell.getSkin() == null ? null : cell.getSkin().getNode(); + if (n instanceof Region) { + Region r = (Region) n; + padding = r.snappedTopInset() + r.snappedBottomInset(); + } + + double maxHeight; + maxHeight = 0; + getChildren().add(cell); + + for (TableColumn column : getSkinnable().getColumns()) { + cell.updateTableColumn(column); + cell.updateTableView(handle.getGridView()); + cell.updateIndex(row); + + if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) { + cell.setWrapText(true); + + cell.impl_processCSS(false); + maxHeight = Math.max(maxHeight, cell.prefHeight(column.getWidth())); + } + } + getChildren().remove(cell); + rowHeightMap.put(row, maxHeight + padding); + Event.fireEvent(spreadsheetView, new SpreadsheetView.RowHeightEvent(row, maxHeight + padding)); + + rectangleSelection.updateRectangle(); + } + + public void resizeRowsToMaximum() { + //First we resize to fit. + resizeRowsToFitContent(); + + Grid grid = spreadsheetView.getGrid(); + + //Then we take the maximum and apply it everywhere. + double maxHeight = 0; + for(int key:rowHeightMap.keySet()){ + maxHeight = Math.max(maxHeight, rowHeightMap.get(key)); + } + + rowHeightMap.clear(); + int maxRows = handle.getView().getGrid().getRowCount(); + for (int row = 0; row < maxRows; row++) { + if (grid.isRowResizable(row)) { + Event.fireEvent(spreadsheetView, new SpreadsheetView.RowHeightEvent(row, maxHeight)); + rowHeightMap.put(row, maxHeight); + } + } + rectangleSelection.updateRectangle(); + } + + public void resizeRowsToDefault() { + rowHeightMap.clear(); + Grid grid = spreadsheetView.getGrid(); + /** + * When resizing to default, we need to go through the visible rows in + * order to update them directly. Because if the rowHeightMap is empty, + * the rows will not detect that maybe the height has changed. + */ + for (GridRow row : (List<GridRow>) getFlow().getCells()) { + if (grid.isRowResizable(row.getIndex())) { + double newHeight = row.computePrefHeight(-1); + if (row.getPrefHeight() != newHeight) { + row.setRowHeight(newHeight); + row.requestLayout(); + } + } + } + + //Fixing https://bitbucket.org/controlsfx/controlsfx/issue/358/ + getFlow().layoutChildren(); + + for (GridRow row : (List<GridRow>) getFlow().getCells()) { + double height = getRowHeight(row.getIndex()); + if (row.getHeight() != height) { + if (grid.isRowResizable(row.getIndex())) { + row.setRowHeight(height); + } + } + } + rectangleSelection.updateRectangle(); + } + /** + * We want to have extra space when displaying LocalDate because they will + * use an editor that display a little icon on the right. Thus, that icon is + * reducing the visibility of the date string. + */ + @Override + public void resizeColumnToFitContent(TableColumn<ObservableList<SpreadsheetCell>, ?> tc, int maxRows) { + + final TableColumn<ObservableList<SpreadsheetCell>, ?> col = tc; + List<?> items = itemsProperty().get(); + if (items == null || items.isEmpty()) { + return; + } + + Callback/* <TableColumn<T, ?>, TableCell<T,?>> */ cellFactory = col.getCellFactory(); + if (cellFactory == null) { + return; + } + + TableCell<ObservableList<SpreadsheetCell>, ?> cell = (TableCell<ObservableList<SpreadsheetCell>, ?>) cellFactory + .call(col); + if (cell == null) { + return; + } + + //The current index of that column + int indexColumn = handle.getGridView().getColumns().indexOf(tc); + + /** + * This is to prevent resize of columns that have the same default width + * at initialisation. If the "system" is calling this method, the + * maxRows will be set at 30. When we set a prefWidth and it's equal to + * the "default width", the system wants to resize the column. We must + * prevent that, thus we check if the two conditions are met. + */ + if(maxRows == 30 && handle.isColumnWidthSet(indexColumn)){ + return; + } + + // set this property to tell the TableCell we want to know its actual + // preferred width, not the width of the associated TableColumnBase + cell.getProperties().put("deferToParentPrefWidth", Boolean.TRUE); //$NON-NLS-1$ + + // determine cell padding + double padding = 10; + Node n = cell.getSkin() == null ? null : cell.getSkin().getNode(); + if (n instanceof Region) { + Region r = (Region) n; + padding = r.snappedLeftInset() + r.snappedRightInset(); + } + + ObservableList<ObservableList<SpreadsheetCell>> gridRows = spreadsheetView.getGrid().getRows();//.get(row) + + /** + * If maxRows is -1, we take all rows. If it's 30, it means it's coming + * from TableColumnHeader during initialization, so we push it to 100. + */ + int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows == 30 ? 100 : maxRows); + double maxWidth = 0; + boolean datePresent = false; + cell.updateTableColumn(col); + cell.updateTableView(handle.getGridView()); + /** + * Sometime the skin is not set, and the width computed is zero which + * destroy the grid... So in that case, we manually set the skin... + */ + if (cell.getSkin() == null) { + cell.setSkin(new CellViewSkin((TableCell<ObservableList<SpreadsheetCell>, SpreadsheetCell>) cell)); + } + for (int row = 0; row < rows; row++) { + cell.updateIndex(row); + + if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) { + getChildren().add(cell); + + if (((SpreadsheetCell) cell.getItem()).getItem() instanceof LocalDate) { + datePresent = true; + } + cell.impl_processCSS(false); + double width = cell.prefWidth(-1); + /** + * If the cell is spanning in column, we need to take the other + * columns into account in the calculation of the width. So we + * compute the width needed by the cell and we substract the + * other columns width. + * + * Also if the cell considered is not in the column, we still + * have to compute because a previous column may have based its + * calculation on the current width which will be modified. + */ + SpreadsheetCell spc = gridRows.get(row).get(indexColumn); + if (spc.getColumnSpan() > 1) { + for (int i = spc.getColumn(); i < spc.getColumn() + spc.getColumnSpan(); ++i) { + if(i != indexColumn){ + width -= spreadsheetView.getColumns().get(i).getWidth(); + } + } + } + maxWidth = Math.max(maxWidth, width); + getChildren().remove(cell); + } + } + + // dispose of the cell to prevent it retaining listeners (see RT-31015) + cell.updateIndex(-1); + + // RT-23486 + double widthMax = maxWidth + padding; + if (handle.getGridView().getColumnResizePolicy() == TableView.CONSTRAINED_RESIZE_POLICY) { + widthMax = Math.max(widthMax, col.getWidth()); + } + + if (datePresent && widthMax < DATE_CELL_MIN_WIDTH) { + widthMax = DATE_CELL_MIN_WIDTH; + } + /** + * This method is called by the system at initialisation and later by + * some methods that check wether the specified column is resizable. So + * we do not check if the column is resizable because it will be checked + * before. If we end up here, it either means the column is resizable, + * OR this is the initialisation and we haven't set a specific width so + * we just compute one time the correct width for that column, and once + * set, it will not be called again. + * + * Also, if the prefWidth has already been set but the user resized the + * column with his mouse, we must force the column to resize because + * setting the prefWidth again will not trigger the listeners. + */ + widthMax = snapSize(widthMax); + if (col.getPrefWidth() == widthMax && col.getWidth() != widthMax) { + col.impl_setWidth(widthMax); + } else { + col.setPrefWidth(widthMax); + } + + rectangleSelection.updateRectangle(); + } + + /*************************************************************************** + * * PRIVATE/PROTECTED METHOD * * + **************************************************************************/ + protected final void init() { + rectangleSelection = new RectangleSelection(this, (TableViewSpanSelectionModel) handle.getGridView().getSelectionModel()); + getFlow().getVerticalBar().valueProperty().addListener(vbarValueListener); + verticalHeader = new VerticalHeader(handle); + getChildren().add(verticalHeader); + + ((HorizontalHeader) getTableHeaderRow()).init(); + verticalHeader.init(this, (HorizontalHeader) getTableHeaderRow()); + + horizontalPickers = new HorizontalPicker((HorizontalHeader) getTableHeaderRow(), spreadsheetView); + getChildren().add(horizontalPickers); + getFlow().init(spreadsheetView); + ((GridViewBehavior)getBehavior()).setGridViewSkin(this); + } + + protected final ObservableSet<Integer> getCurrentlyFixedRow() { + return currentlyFixedRow; + } + + /** + * Used in the HorizontalColumnHeader when we need to resize in double + * click. + * + * @param tc + * @param maxRows + */ + public void resize(TableColumnBase<?, ?> tc, int maxRows) { + if(tc.isResizable()){ + int columnIndex = getColumns().indexOf(tc); + TableColumn tableColumn = getColumns().get(columnIndex); + resizeColumnToFitContent(tableColumn, maxRows); + Event.fireEvent(spreadsheetView, new SpreadsheetView.ColumnWidthEvent(columnIndex, tableColumn.getWidth())); + } + } + + @Override + protected void layoutChildren(double x, double y, double w, final double h) { + if (spreadsheetView == null) { + return; + } + double verticalHeaderWidth = verticalHeader.computeHeaderWidth(); + double horizontalPickerHeight = spreadsheetView.getColumnPickers().isEmpty() ? 0: VerticalHeader.PICKER_SIZE; + + if (spreadsheetView.isShowRowHeader() || !spreadsheetView.getRowPickers().isEmpty()) { + x += verticalHeaderWidth; + w -= verticalHeaderWidth; + } else { + x = 0.0; + } + + + y += horizontalPickerHeight; + super.layoutChildren(x, y, w, h-horizontalPickerHeight); + + final double baselineOffset = getSkinnable().getLayoutBounds().getHeight() / 2; + double tableHeaderRowHeight = 0; + + if(!spreadsheetView.getColumnPickers().isEmpty()){ + layoutInArea(horizontalPickers, x, y - VerticalHeader.PICKER_SIZE, w, tableHeaderRowHeight, baselineOffset, HPos.CENTER, VPos.CENTER); + } + + if (spreadsheetView.showColumnHeaderProperty().get()) { + // position the table header + tableHeaderRowHeight = getTableHeaderRow().prefHeight(-1); + layoutInArea(getTableHeaderRow(), x, y, w, tableHeaderRowHeight, baselineOffset, HPos.CENTER, VPos.CENTER); + + y += tableHeaderRowHeight; + } else { + // This is temporary handled in the HorizontalHeader with Css + // FIXME tweak open in https://javafx-jira.kenai.com/browse/RT-32673 + } + + if (spreadsheetView.isShowRowHeader() || !spreadsheetView.getRowPickers().isEmpty()) { + layoutInArea(verticalHeader, x - verticalHeaderWidth, y - tableHeaderRowHeight, w, h, baselineOffset, + HPos.CENTER, VPos.CENTER); + } + } + + @Override + protected void onFocusPreviousCell() { + focusScroll(); + } + + @Override + protected void onFocusNextCell() { + focusScroll(); + } + + void focusScroll() { + final TableFocusModel<?, ?> fm = getFocusModel(); + if (fm == null) { + return; + } + /** + * *************************************************************** + * MODIFIED + **************************************************************** + */ + final int row = fm.getFocusedIndex(); + // We try to make visible the rows that may be hidden by Fixed rows + if (!getFlow().getCells().isEmpty() + && getFlow().getCells().get(spreadsheetView.getFixedRows().size()).getIndex() > row + && !spreadsheetView.getFixedRows().contains(row)) { + flow.scrollTo(row); + } else { + flow.show(row); + } + scrollHorizontally(); + /** + * *************************************************************** + * END OF MODIFIED + **************************************************************** + */ + } + + @Override + protected void onSelectPreviousCell() { + super.onSelectPreviousCell(); + scrollHorizontally(); + } + + @Override + protected void onSelectNextCell() { + super.onSelectNextCell(); + scrollHorizontally(); + } + + @Override + protected VirtualFlow<TableRow<ObservableList<SpreadsheetCell>>> createVirtualFlow() { + return new GridVirtualFlow<>(this); + } + + @Override + protected TableHeaderRow createTableHeaderRow() { + return new HorizontalHeader(this); + } + + protected HorizontalHeader getHorizontalHeader(){ + return (HorizontalHeader) getTableHeaderRow(); + } + + BooleanProperty getTableMenuButtonVisibleProperty() { + return tableMenuButtonVisibleProperty(); + } + + @Override + public void scrollHorizontally(){ + super.scrollHorizontally(); + } + + @Override + protected void scrollHorizontally(TableColumn<ObservableList<SpreadsheetCell>, ?> col) { + if (col == null || !col.isVisible()) { + return; + } + /** + * We modified this function so that we ensure that any selected cells + * will not be below a fixed column. Because when there's some fixed + * columns, the "left border" is not the table anymore, but the right + * side of the last fixed columns. + * + * Moreover, we need to re-compute the fixedColumnWidth because the + * layout of the rows hasn't been done yet and the value is not right. + * So we might end up below a fixedColumns. + */ + + fixedColumnWidth = 0; + final double pos = getFlow().getHorizontalBar().getValue(); + int index = getColumns().indexOf(col); + double start = 0;// scrollX; + + for (int i = 0; i < index; ++i) { + SpreadsheetColumn column = spreadsheetView.getColumns().get(i); + if (column.isFixed()) { + fixedColumnWidth += column.getWidth(); + } + start += column.getWidth(); + } + + final double end = start + col.getWidth(); + + // determine the visible width of the table + final double headerWidth = handle.getView().getWidth() - snappedLeftInset() - snappedRightInset() - verticalHeader.getVerticalHeaderWidth(); + + // determine by how much we need to translate the table to ensure that + // the start position of this column lines up with the left edge of the + // tableview, and also that the columns don't become detached from the + // right edge of the table + final double max = getFlow().getHorizontalBar().getMax(); + double newPos; + + /** + * If the starting position of our column if inferior to the left egde + * (of tableView or fixed columns), then we need to scroll. + */ + if (start < pos + fixedColumnWidth && start >= 0 && start >= fixedColumnWidth) { + newPos = start - fixedColumnWidth < 0 ? start : start - fixedColumnWidth; + getFlow().getHorizontalBar().setValue(newPos); + //If the starting point is not visible on the right. + } else if(start > pos + headerWidth){ + final double delta = start < 0 || end > headerWidth ? start - pos - fixedColumnWidth : 0; + newPos = pos + delta > max ? max : pos + delta; + getFlow().getHorizontalBar().setValue(newPos); + } + /** + * In all other cases, it means the cell is visible so no scroll needed, + * because otherwise we may end up with a continous scroll that always + * place the selected cell in the center of the screen. + */ + } + + private void verticalScroll() { + verticalHeader.requestLayout(); + } + + GridVirtualFlow<?> getFlow() { + return (GridVirtualFlow<?>) flow; + } + + /** + * Return a BitSet of the rows that needs layout all the time. This + * includes any row containing a span, or a fixed row. + * @return + */ + private BitSet initRowToLayoutBitSet(){ + Grid grid = handle.getView().getGrid(); + BitSet bitSet = new BitSet(grid.getRowCount()); + for(int row = 0;row<grid.getRowCount();++row){ + if(spreadsheetView.getFixedRows().contains(row)){ + bitSet.set(row); + continue; + } + List<SpreadsheetCell> myRow = grid.getRows().get(row); + for(SpreadsheetCell cell:myRow){ + + if(cell.getRowSpan()>1 /*|| cell.getColumnSpan() >1*/){ + bitSet.set(row); + break; + } + } + } + return bitSet; + } + + /** + * When the vertical moves, we update the verticalHeader + */ + private final InvalidationListener vbarValueListener = new InvalidationListener() { + @Override + public void invalidated(Observable valueModel) { + verticalScroll(); + } + }; + + /** + * We listen on the FixedRows in order to do the modification in the + * VirtualFlow + */ + private final ListChangeListener<Integer> fixedRowsListener = new ListChangeListener<Integer>() { + @Override + public void onChanged(Change<? extends Integer> c) { + hBarValue.clear(); + while (c.next()) { + if (c.wasPermutated()) { + for (Integer fixedRow : c.getList()) { + rowToLayout.set(fixedRow, true); + } + } else { + for (Integer unfixedRow : c.getRemoved()) { + rowToLayout.set(unfixedRow, false); + //If the grid permits it, we check the spanning in order not + //to remove a row that might need layout. + if (spreadsheetView.getGrid().getRows().size() > unfixedRow) { + List<SpreadsheetCell> myRow = spreadsheetView.getGrid().getRows().get(unfixedRow); + for (SpreadsheetCell cell : myRow) { + if (cell.getRowSpan() > 1 || cell.getColumnSpan() > 1) { + rowToLayout.set(unfixedRow, true); + break; + } + } + } + } + + //We check for the newly fixedRow + for (Integer fixedRow : c.getAddedSubList()) { + rowToLayout.set(fixedRow, true); + } + } + } + // requestLayout() not responding immediately.. + getFlow().requestLayout(); + } + }; + + /** + * We listen on the currentlyFixedRow in order to do the modification in the + * FixedRowHeight. + */ + private final SetChangeListener<? super Integer> currentlyFixedRowListener = new SetChangeListener<Integer>() { + @Override + public void onChanged(javafx.collections.SetChangeListener.Change<? extends Integer> arg0) { + computeFixedRowHeight(); + } + }; + + /** + * We compute the total height of the fixedRows so that the selection can + * use it without performance regression. + */ + public void computeFixedRowHeight() { + fixedRowHeight = 0; + for (int i : getCurrentlyFixedRow()) { + fixedRowHeight += getRowHeight(i); + } + } + + /** + * We listen on the FixedColumns in order to do the modification in the + * VirtualFlow. + */ + private final ListChangeListener<SpreadsheetColumn> fixedColumnsListener = new ListChangeListener<SpreadsheetColumn>() { + @Override + public void onChanged(Change<? extends SpreadsheetColumn> c) { + hBarValue.clear(); + getFlow().requestLayout(); + // requestLayout() not responding immediately.. +// getFlow().layoutTotal(); + } + }; + + @Override + protected TableSelectionModel<ObservableList<SpreadsheetCell>> getSelectionModel() { + return getSkinnable().getSelectionModel(); + } + + @Override + protected TableFocusModel<ObservableList<SpreadsheetCell>, TableColumn<ObservableList<SpreadsheetCell>, ?>> getFocusModel() { + return getSkinnable().getFocusModel(); + } + + @Override + protected TablePositionBase<? extends TableColumn<ObservableList<SpreadsheetCell>, ?>> getFocusedCell() { + return getSkinnable().getFocusModel().getFocusedCell(); + } + + @Override + protected ObservableList<? extends TableColumn<ObservableList<SpreadsheetCell>, ?>> getVisibleLeafColumns() { + return getSkinnable().getVisibleLeafColumns(); + } + + @Override + protected int getVisibleLeafIndex(TableColumn<ObservableList<SpreadsheetCell>, ?> tc) { + return getSkinnable().getVisibleLeafIndex(tc); + } + + @Override + protected TableColumn<ObservableList<SpreadsheetCell>, ?> getVisibleLeafColumn(int col) { + return getSkinnable().getVisibleLeafColumn(col); + } + + @Override + protected ObservableList<TableColumn<ObservableList<SpreadsheetCell>, ?>> getColumns() { + return getSkinnable().getColumns(); + } + + @Override + protected ObservableList<TableColumn<ObservableList<SpreadsheetCell>, ?>> getSortOrder() { + return getSkinnable().getSortOrder(); + } + + @Override + protected ObjectProperty<ObservableList<ObservableList<SpreadsheetCell>>> itemsProperty() { + return getSkinnable().itemsProperty(); + } + + @Override + protected ObjectProperty<Callback<TableView<ObservableList<SpreadsheetCell>>, TableRow<ObservableList<SpreadsheetCell>>>> rowFactoryProperty() { + return getSkinnable().rowFactoryProperty(); + } + + @Override + protected ObjectProperty<Node> placeholderProperty() { + return getSkinnable().placeholderProperty(); + } + + @Override + protected BooleanProperty tableMenuButtonVisibleProperty() { + return getSkinnable().tableMenuButtonVisibleProperty(); + } + + @Override + protected ObjectProperty<Callback<ResizeFeaturesBase, Boolean>> columnResizePolicyProperty() { + return (ObjectProperty<Callback<ResizeFeaturesBase, Boolean>>) (Object)getSkinnable().columnResizePolicyProperty(); + } + + @Override + protected boolean resizeColumn(TableColumn<ObservableList<SpreadsheetCell>, ?> tc, double delta) { + getHorizontalHeader().getRootHeader().lastColumnResized = getColumns().indexOf(tc); + boolean returnedValue = getSkinnable().resizeColumn(tc, delta); + if(returnedValue){ + Event.fireEvent(spreadsheetView, new SpreadsheetView.ColumnWidthEvent(getColumns().indexOf(tc), tc.getWidth())); + } + return returnedValue; + } + + @Override + protected void edit(int index, TableColumn<ObservableList<SpreadsheetCell>, ?> column) { + getSkinnable().edit(index, column); + } + + @Override + public TableRow<ObservableList<SpreadsheetCell>> createCell() { + TableRow<ObservableList<SpreadsheetCell>> cell; + + if (getSkinnable().getRowFactory() != null) { + cell = getSkinnable().getRowFactory().call(getSkinnable()); + } else { + cell = new TableRow<>(); + } + + cell.updateTableView(getSkinnable()); + return cell; + } + + @Override + public int getItemCount() { + return getSkinnable().getItems() == null ? 0 : getSkinnable().getItems().size(); + } + + /** + * If the scene is not yet instantiated, we need to wait otherwise the + * VirtualFlow will not shift the cells properly. + * + * @param value + */ + public void setHbarValue(double value) { + setHbarValue(value, 0); + } + + public void setHbarValue(double value, int count) { + if (count > 5) { + return; + } + final int newCount = count + 1; + if (flow.getScene() == null) { + Platform.runLater(() -> { + setHbarValue(value, newCount); + }); + return; + } + getHBar().setValue(value); + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/GridVirtualFlow.java b/src/impl/org/controlsfx/spreadsheet/GridVirtualFlow.java new file mode 100644 index 0000000000000000000000000000000000000000..ea549458ebb9a29a4b6541cddd93fd474cef13fe --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/GridVirtualFlow.java @@ -0,0 +1,426 @@ +/** + * Copyright (c) 2013, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.scene.control.skin.VirtualFlow; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import javafx.beans.Observable; +import javafx.beans.binding.When; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.scene.Group; +import javafx.scene.Node; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.ScrollBar; +import javafx.scene.control.TableRow; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import javafx.scene.shape.Rectangle; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +public final class GridVirtualFlow<T extends IndexedCell<?>> extends VirtualFlow<T> { + + /** + * With that comparator we can lay out our rows in the reverse order. That + * is to say from the bottom to the very top. In that manner we are sure + * that our spanning cells will COVER the cell below so we don't have any + * problems with missing hovering, the editor jammed etc. + * <br/> + * + * The only problem is for the fixed column but the {@link #getTopRow(int) } + * now returns the very first row and allow us to put some privileged + * TableCell in it if they feel the need to be on top in term of z-order. + * + * FIXME The best would be to put a TreeList of something like that in order + * not to sort the rows everytime, need investigation.. + */ + private static final Comparator<GridRow> ROWCMP = new Comparator<GridRow>() { + @Override + public int compare(GridRow firstRow, GridRow secondRow) { + //o1.getIndex() < o2.getIndex() ? -1 : +1; + return secondRow.getIndex() - firstRow.getIndex(); + } + }; + + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private SpreadsheetView spreadSheetView; + private final GridViewSkin gridViewSkin; + /** + * Store the fixedRow in order to place them at the top when necessary. + * That is to say, when the VirtualFlow has not already placed one. + */ + private final ArrayList<T> myFixedCells = new ArrayList<>(); + public final List<Node> sheetChildren; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + public GridVirtualFlow(GridViewSkin gridViewSkin) { + super(); + this.gridViewSkin = gridViewSkin; + final ChangeListener<Number> listenerY = new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) { + layoutTotal(); + } + }; + getVbar().valueProperty().addListener(listenerY); + getHbar().valueProperty().addListener(hBarValueChangeListener); + widthProperty().addListener(hBarValueChangeListener); + + sheetChildren = findSheetChildren(); + + //When we click outside of the grid, we want to deselect all cells. + addEventHandler(MouseEvent.MOUSE_PRESSED, (MouseEvent event) -> { + if (event.getTarget().getClass() == GridRow.class) { + spreadSheetView.getSelectionModel().clearSelection(); + } + }); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + public void init(SpreadsheetView spv) { + /** + * The idea is to work-around + * https://javafx-jira.kenai.com/browse/RT-36396 in order to have the + * same behavior between the vertical scrollBar and the horizontal + * scrollBar. + */ + getHbar().maxProperty().addListener(new ChangeListener<Number>() { + + @Override + public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { + //We want to go page by page. + getHbar().setBlockIncrement(getWidth()); + getHbar().setUnitIncrement(newValue.doubleValue()/20); + } + }); + + this.spreadSheetView = spv; + + //We clip the rectangle selection with a rectangle, inception style. + Rectangle rec = new Rectangle(); + rec.widthProperty().bind(widthProperty().subtract(new When(getVbar().visibleProperty()).then(getVbar().widthProperty()).otherwise(0))); + rec.heightProperty().bind(heightProperty().subtract(new When(getHbar().visibleProperty()).then(getHbar().heightProperty()).otherwise(0))); + gridViewSkin.rectangleSelection.setClip(rec); + + getChildren().add(gridViewSkin.rectangleSelection); + + spv.getFixedRows().addListener((Observable observable) -> { + List<T> toRemove = new ArrayList<>(); + for (T cell : myFixedCells) { + if (!spv.getFixedRows().contains(cell.getIndex())) { + cell.setManaged(false); + cell.setVisible(false); + toRemove.add(cell); + } + } + myFixedCells.removeAll(toRemove); + }); + } + + @Override + public void show(int index) { + super.show(index); + layoutTotal(); + layoutFixedRows(); + } + + @Override + public void scrollTo(int index) { + //If we have some fixedRows, we check if the selected row is not below them + if (!getCells().isEmpty() && spreadSheetView.getFixedRows().size() > 0) { + double offset = gridViewSkin.getFixedRowHeight(); + + while (offset >= 0 && index > 0) { + index--; + offset -= gridViewSkin.getRowHeight(index); + } + } + super.scrollTo(index); + + layoutTotal(); + layoutFixedRows(); + } + + @Override + public double adjustPixels(final double delta) { + final double returnValue = super.adjustPixels(delta); + + layoutTotal(); + layoutFixedRows(); + + return returnValue; + } + + List<T> getFixedCells(){ + return myFixedCells; + } + /*************************************************************************** + * * Protected Methods * * + **************************************************************************/ + + /** + * We need to return here the very top row in term of "z-order". Because we + * will add in this row the TableCell that are in fixedColumn and which + * needs to be drawn on top of all others. + * + * @return + */ + GridRow getTopRow() { + if (!sheetChildren.isEmpty()) { + return (GridRow) sheetChildren.get(sheetChildren.size() - 1); + } + return null; + } + + @Override + protected void layoutChildren() { + /** + * In fact, we must do a layout even when editing, because if the user + * resize the window during edition, if we block layout, the view will + * be in a wrong state. + */ + if (spreadSheetView != null + /*&& (spreadSheetView.getEditingCell() == null || spreadSheetView + .getEditingCell().getRow() == -1)*/) { + sortRows(); + super.layoutChildren(); + layoutTotal(); + layoutFixedRows(); + + /** + * Sometimes, the visible amount is not computed when we have few + * big rows. If we detect that case, we must compute it manually + * otherwise the Vbar is wrongly set. + */ + if (getVbar().getVisibleAmount() == 0.0 + && getVbar().isVisible() + && getCells().size() != getCellCount()) { + getVbar().setMax(1); + getVbar().setVisibleAmount(getCells().size() / (float) getCellCount()); + } + } + } + + /** + * Layout all the visible rows + */ + protected void layoutTotal() { + sortRows(); + + /** + * When we layout, we also remove the cell that have been deported into + * other rows in order not to have some TableCell hanging out. + */ + for(GridRow row : gridViewSkin.deportedCells.keySet()){ + for(CellView cell: gridViewSkin.deportedCells.get(row)){ + row.removeCell(cell); + } + } + gridViewSkin.deportedCells.clear(); + // When scrolling fast with fixed Rows, cells is empty and not recreated.. + if (getCells().isEmpty()) { + reconfigureCells(); + } + + for (GridRow cell : (List<GridRow>)getCells()) { + if (cell != null && cell.getIndex() >= 0 && (!gridViewSkin.hBarValue.get(cell.getIndex()) || gridViewSkin.rowToLayout.get(cell.getIndex()))) { + cell.requestLayout(); + } + } + } + + protected ScrollBar getVerticalBar() { + return getVbar(); + } + protected ScrollBar getHorizontalBar() { + return getHbar(); + } + + @Override + protected List<T> getCells() { + return super.getCells(); + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + /** + * WARNING : This is bad but no other options right now. This will find the + * sheetChildren of the VirtualFlow, aka where the cells are kept and + * clipped. See layoutFixedRows() or getTopRow() for use. + * + * @return + */ + private List<Node> findSheetChildren(){ + if(!getChildren().isEmpty()){ + if(getChildren().get(0) instanceof Region){ + Region region = (Region) getChildren().get(0); + if(!region.getChildrenUnmodifiable().isEmpty()){ + if(region.getChildrenUnmodifiable().get(0) instanceof Group){ + return ((Group)region.getChildrenUnmodifiable().get(0)).getChildren(); + } + } + } + } + return new ArrayList<>(); + } + + /** + * Layout the fixed rows to position them correctly + */ + private void layoutFixedRows() { + + //We must have a cell in ViewPort because otherwise + //we short-circuit the VirtualFlow. + if (spreadSheetView.getFixedRows().size() > 0 && getFirstVisibleCellWithinViewPort() != null) { + sortRows(); + /** + * What I do is just going after the VirtualFlow in order to ADD + * (not replace like before) new rows at the top. + * + * If the VirtualFlow has the row, then I will hide mine and let him + * handle. But if the row is missing, then I must show mine in order + * to have the fixed row. + */ + T row = null; + Integer fixedRowIndex; + + rows: + for (int i = spreadSheetView.getFixedRows().size() - 1; i >= 0; i--) { + fixedRowIndex = spreadSheetView.getFixedRows().get(i); + T lastCell = getLastVisibleCellWithinViewPort(); + //If the fixed row is out of bounds + if (lastCell != null && fixedRowIndex > lastCell.getIndex()) { + if (row != null) { + row.setVisible(false); + row.setManaged(false); + sheetChildren.remove(row); + } + continue; + } + + //We see if the row is laid out by the VirtualFlow + for (T virtualFlowCells : getCells()) { + if (virtualFlowCells.getIndex() > fixedRowIndex) { + break; + } else if (virtualFlowCells.getIndex() == fixedRowIndex) { + row = containsRows(fixedRowIndex); + if (row != null) { + row.setVisible(false); + row.setManaged(false); + sheetChildren.remove(row); + } + /** + * OLD COMMENT : We must push to Front only if the row + * is at the very top and has a risk to be recovered. + * This is happening only if this row is translated. + * + * NEW COMMENT: I'm not sure about this.. Since the + * fixedColumn are not in the special top row, we don't + * care if the row is pushed to front.. need + * investigation + */ + virtualFlowCells.toFront(); + continue rows; + } + } + + row = containsRows(fixedRowIndex); + if (row == null) { + /** + * getAvailableCell is not added our cell to the ViewPort in some cases. + * So we need to instantiate it ourselves. + */ + row = getCreateCell().call(this); + row.getProperties().put("newcell", null); //$NON-NLS-1$ + + setCellIndex(row, fixedRowIndex); + resizeCellSize(row); + myFixedCells.add(row); + } + + /** + * Sometime, when we set a new Grid on a SpreadsheetView without recreating it, + * we can end up with some rows not being added to the ViewPort. + * So we must be sure it's in and add it ourself otherwise. + */ + if(!sheetChildren.contains(row)){ + sheetChildren.add(row); + } + + row.setManaged(true); + row.setVisible(true); + row.toFront(); + row.requestLayout(); + } + } + } + + /** + * Verify if the row has been added to myFixedCell + * @param i + * @return + */ + private T containsRows(int i){ + for(T cell:myFixedCells){ + if(cell.getIndex() == i) + return cell; + } + return null; + } + /** + * Sort the rows so that they stay in order for layout + */ + private void sortRows() { + final List<GridRow> temp = (List<GridRow>) getCells(); + final List<GridRow> tset = new ArrayList<>(temp); + Collections.sort(tset, ROWCMP); + for (final TableRow<ObservableList<SpreadsheetCell>> r : tset) { + r.toFront(); + } + } + + private final ChangeListener<Number> hBarValueChangeListener = new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) { + gridViewSkin.hBarValue.clear(); + } + }; +} + diff --git a/src/impl/org/controlsfx/spreadsheet/HorizontalHeader.java b/src/impl/org/controlsfx/spreadsheet/HorizontalHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..6540da870224f6196cbb20f6c63401d5e047cc66 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/HorizontalHeader.java @@ -0,0 +1,349 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.scene.control.skin.NestedTableColumnHeader; +import com.sun.javafx.scene.control.skin.TableColumnHeader; +import com.sun.javafx.scene.control.skin.TableHeaderRow; +import java.util.BitSet; +import java.util.List; +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView.TableViewSelectionModel; +import javafx.scene.input.MouseEvent; +import javafx.scene.shape.Rectangle; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetColumn; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * The set of horizontal (column) headers. + */ +public class HorizontalHeader extends TableHeaderRow { + + final GridViewSkin gridViewSkin; + + // Indicate whether the this HorizontalHeader is activated or not + private boolean working = true; + /** + * When the columns header are clicked, we consider the column as selected. + * This BitSet is reset when a modification on cells is done. + */ + protected BitSet selectedColumns = new BitSet(); + + /*************************************************************************** + * + * Constructor + * + **************************************************************************/ + public HorizontalHeader(final GridViewSkin skin) { + super(skin); + gridViewSkin = skin; + } + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + public void init() { + SpreadsheetView spv = gridViewSkin.handle.getView(); + updateHorizontalHeaderVisibility(spv.isShowColumnHeader()); + + //Visibility of vertical Header listener + spv.showRowHeaderProperty().addListener(verticalHeaderListener); + gridViewSkin.verticalHeader.verticalHeaderWidthProperty().addListener(verticalHeaderListener); + + //Visibility of horizontal Header listener + spv.showColumnHeaderProperty().addListener(horizontalHeaderVisibilityListener); + + //Selection listener to highlight header + gridViewSkin.getSelectedColumns().addListener(selectionListener); + + //Fixed Column listener to change style of header + spv.getFixedColumns().addListener(fixedColumnsListener); + + Platform.runLater(() -> { + //We are doing that because some columns may be already fixed. + for (SpreadsheetColumn column : spv.getFixedColumns()) { + fixColumn(column); + } + requestLayout(); + /** + * Clicking on header select the whole column. + */ + installHeaderMouseEvent(); + }); + + /** + * When we are setting a new Grid (model) on the SpreadsheetView, it + * appears that the headers are re-created. So we need to listen to + * those changes in order to re-apply our css style class. Otherwise + * we'll end up with fixedColumns but no graphic confirmation. + */ + getRootHeader().getColumnHeaders().addListener((Observable o) -> { + for (SpreadsheetColumn fixItem : spv.getFixedColumns()) { + fixColumn(fixItem); + } + updateHighlightSelection(); + installHeaderMouseEvent(); + }); + } + + @Override + public HorizontalHeaderColumn getRootHeader() { + return (HorizontalHeaderColumn) super.getRootHeader(); + } + + void clearSelectedColumns(){ + selectedColumns.clear(); + } + /************************************************************************** + * + * Protected methods + * + **************************************************************************/ + @Override + protected void updateTableWidth() { + super.updateTableWidth(); + // snapping added for RT-19428 + double padding = 0; + + if (working && gridViewSkin != null + && gridViewSkin.spreadsheetView != null + && gridViewSkin.spreadsheetView.showRowHeaderProperty().get() + && gridViewSkin.verticalHeader != null) { + padding += gridViewSkin.verticalHeader.getVerticalHeaderWidth(); + } + + Rectangle clip = ((Rectangle) getClip()); + + clip.setWidth(clip.getWidth() == 0 ? 0 : clip.getWidth() - padding); + } + + @Override + protected void updateScrollX() { + super.updateScrollX(); + gridViewSkin.horizontalPickers.updateScrollX(); + + if (working) { + requestLayout(); + getRootHeader().layoutFixedColumns(); + } + } + + @Override + protected NestedTableColumnHeader createRootHeader() { + return new HorizontalHeaderColumn(getTableSkin(), null); + } + + /************************************************************************** + * + * Private methods. + * + **************************************************************************/ + + /** + * When we click on header, we want to select the whole column. + */ + private void installHeaderMouseEvent() { + for (final TableColumnHeader columnHeader : getRootHeader().getColumnHeaders()) { + EventHandler<MouseEvent> mouseEventHandler = (MouseEvent mouseEvent) -> { + if (mouseEvent.isPrimaryButtonDown()) { + headerClicked((TableColumn) columnHeader.getTableColumn(), mouseEvent); + } + }; + columnHeader.getChildrenUnmodifiable().get(0).setOnMousePressed(mouseEventHandler); + } + } + /** + * If a header is clicked, we must select the whole column. If Control key of + * Shift key is pressed, we must not deselect the previous selection but + * just act like the {@link GridViewBehavior} would. + * + * @param column + * @param event + */ + private void headerClicked(TableColumn column, MouseEvent event) { + TableViewSelectionModel<ObservableList<SpreadsheetCell>> sm = gridViewSkin.handle.getGridView().getSelectionModel(); + int lastRow = gridViewSkin.spreadsheetView.getGrid().getRowCount() - 1; + int indexColumn = column.getTableView().getColumns().indexOf(column); + TablePosition focusedPosition = sm.getTableView().getFocusModel().getFocusedCell(); + if (event.isShortcutDown()) { + BitSet tempSet = (BitSet) selectedColumns.clone(); + sm.selectRange(0, column, lastRow, column); + selectedColumns.or(tempSet); + selectedColumns.set(indexColumn); + } else if (event.isShiftDown() && focusedPosition != null && focusedPosition.getTableColumn() != null) { + sm.clearSelection(); + sm.selectRange(0, column, lastRow, focusedPosition.getTableColumn()); + sm.getTableView().getFocusModel().focus(0, focusedPosition.getTableColumn()); + int min = Math.min(indexColumn, focusedPosition.getColumn()); + int max = Math.max(indexColumn, focusedPosition.getColumn()); + selectedColumns.set(min, max + 1); + } else { + sm.clearSelection(); + sm.selectRange(0, column, lastRow, column); + //And we want to have the focus on the first cell in order to be able to copy/paste between columns. + sm.getTableView().getFocusModel().focus(0, column); + selectedColumns.set(indexColumn); + } + } + /** + * Whether the Vertical Header is showing, we need to update the width + * because some space on the left will be available/used. + */ + private final InvalidationListener verticalHeaderListener = new InvalidationListener() { + + @Override + public void invalidated(Observable observable) { + updateTableWidth(); + } + }; + + /** + * Whether the Horizontal Header is showing, we need to toggle its + * visibility. + */ + private final ChangeListener<Boolean> horizontalHeaderVisibilityListener = new ChangeListener<Boolean>() { + @Override + public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) { + updateHorizontalHeaderVisibility(arg2); + } + }; + + /** + * When we fix/unfix some columns, we change the style of the Label header + * text + */ + private final ListChangeListener<SpreadsheetColumn> fixedColumnsListener = new ListChangeListener<SpreadsheetColumn>() { + + @Override + public void onChanged(javafx.collections.ListChangeListener.Change<? extends SpreadsheetColumn> change) { + while (change.next()) { + //If we unfix a column + for (SpreadsheetColumn remitem : change.getRemoved()) { + unfixColumn(remitem); + } + //If we fix one + for (SpreadsheetColumn additem : change.getAddedSubList()) { + fixColumn(additem); + } + } + updateHighlightSelection(); + } + }; + + /** + * Fix this column regarding the style + * + * @param column + */ + private void fixColumn(SpreadsheetColumn column) { + addStyleHeader(gridViewSkin.spreadsheetView.getColumns().indexOf(column)); + } + + /** + * Unfix this column regarding the style + * + * @param column + */ + private void unfixColumn(SpreadsheetColumn column) { + removeStyleHeader(gridViewSkin.spreadsheetView.getColumns().indexOf(column)); + } + + /** + * Add the fix style of the header Label of the specified column + * + * @param i + */ + private void removeStyleHeader(Integer i) { + if (getRootHeader().getColumnHeaders().size() > i) { + getRootHeader().getColumnHeaders().get(i).getStyleClass().removeAll("fixed"); //$NON-NLS-1$ + } + } + + /** + * Remove the fix style of the header Label of the specified column + * + * @param i + */ + private void addStyleHeader(Integer i) { + if (getRootHeader().getColumnHeaders().size() > i) { + getRootHeader().getColumnHeaders().get(i).getStyleClass().addAll("fixed"); //$NON-NLS-1$ + } + } + + /** + * When we select some cells, we want the header to be highlighted + */ + private final InvalidationListener selectionListener = new InvalidationListener() { + @Override + public void invalidated(Observable valueModel) { + updateHighlightSelection(); + } + }; + + /** + * Highlight the header Label when selection change. + */ + private void updateHighlightSelection() { + for (final TableColumnHeader i : getRootHeader().getColumnHeaders()) { + i.getStyleClass().removeAll("selected"); //$NON-NLS-1$ + + } + final List<Integer> selectedColumns = gridViewSkin.getSelectedColumns(); + for (final Integer i : selectedColumns) { + if (getRootHeader().getColumnHeaders().size() > i) { + getRootHeader().getColumnHeaders().get(i).getStyleClass() + .addAll("selected"); //$NON-NLS-1$ + } + } + + } + + private void updateHorizontalHeaderVisibility(boolean visible) { + working = visible; + setManaged(working); + if (!visible) { + getStyleClass().add("invisible"); //$NON-NLS-1$ + } else { + getStyleClass().remove("invisible"); //$NON-NLS-1$ + requestLayout(); + getRootHeader().layoutFixedColumns(); + updateHighlightSelection(); + } + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/HorizontalHeaderColumn.java b/src/impl/org/controlsfx/spreadsheet/HorizontalHeaderColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..a8d7726980becdd302d6950f70f2a8d02a638709 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/HorizontalHeaderColumn.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +import javafx.event.EventHandler; +import javafx.scene.control.TableColumnBase; +import javafx.scene.input.MouseEvent; + +import com.sun.javafx.scene.control.skin.NestedTableColumnHeader; +import com.sun.javafx.scene.control.skin.TableColumnHeader; +import com.sun.javafx.scene.control.skin.TableViewSkinBase; +import javafx.beans.Observable; +import javafx.beans.value.ObservableValue; + +/** + * A cell column header. + */ +public class HorizontalHeaderColumn extends NestedTableColumnHeader { + + int lastColumnResized = -1; + + public HorizontalHeaderColumn( + TableViewSkinBase<?, ?, ?, ?, ?, ?> skin, TableColumnBase<?, ?> tc) { + super(skin, tc); + /** + * Resolve https://bitbucket.org/controlsfx/controlsfx/issue/395 + * and https://bitbucket.org/controlsfx/controlsfx/issue/434 + */ + widthProperty().addListener((Observable observable) -> { + ((GridViewSkin)skin).hBarValue.clear(); + ((GridViewSkin)skin).rectangleSelection.updateRectangle(); + }); + + /** + * We want to resize all other selected columns when we resize one. + * + * I cannot really determine when a resize is finished. Apparently, when + * this variable Layout is set to 0, it means the drag is done, so until + * a beter solution is shown, it will do the trick. + */ + columnReorderLine.layoutXProperty().addListener((ObservableValue<? extends Number> observable, Number oldValue, Number newValue) -> { + HorizontalHeader headerRow = (HorizontalHeader) ((GridViewSkin) skin).getTableHeaderRow(); + GridViewSkin mySkin = ((GridViewSkin) skin); + if (newValue.intValue() == 0 && lastColumnResized >= 0) { + if (headerRow.selectedColumns.get(lastColumnResized)) { + double width1 = mySkin.getColumns().get(lastColumnResized).getWidth(); + for (int i = headerRow.selectedColumns.nextSetBit(0); i >= 0; i = headerRow.selectedColumns.nextSetBit(i + 1)) { + mySkin.getColumns().get(i).setPrefWidth(width1); + } + } + } + }); + } + + @Override + protected TableColumnHeader createTableColumnHeader(final TableColumnBase col) { + TableViewSkinBase<?,?,?,?,?,TableColumnBase<?,?>> tableViewSkin = getTableViewSkin(); + if (col.getColumns().isEmpty()) { + final TableColumnHeader columnHeader = new TableColumnHeader(tableViewSkin, col); + /** + * When the user double click on a header, we want to resize the + * column to fit the content. + */ + columnHeader.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent mouseEvent) { + if (mouseEvent.getClickCount() == 2 && mouseEvent.isPrimaryButtonDown()) { + ((GridViewSkin) (Object) tableViewSkin).resize(col, -1); + } + } + }); + return columnHeader; + } else { + return new HorizontalHeaderColumn(getTableViewSkin(), col); + } + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); + layoutFixedColumns(); + } + + /** + * We want ColumnHeader to be fixed when we freeze some columns + * + */ + public void layoutFixedColumns() { + SpreadsheetHandle handle = ((GridViewSkin) (Object) getTableViewSkin()).handle; + final SpreadsheetView spreadsheetView = handle.getView(); + if (handle.getCellsViewSkin() == null || getChildren().isEmpty()) { + return; + } + double hbarValue = handle.getCellsViewSkin().getHBar().getValue(); + + final int labelHeight = (int) getChildren().get(0).prefHeight(-1); + double fixedColumnWidth = 0; + double x = snappedLeftInset(); + int max = getColumnHeaders().size(); + max = max > spreadsheetView.getColumns().size() ? spreadsheetView.getColumns().size() : max; + for (int j = 0 ; j < max; j++) { + final TableColumnHeader n = getColumnHeaders().get(j); + final double prefWidth = snapSize(n.prefWidth(-1)); + n.setPrefHeight(24.0); + //If the column is fixed + if (spreadsheetView.getFixedColumns().indexOf(spreadsheetView.getColumns().get(j)) != -1) { + double tableCellX = 0; + //If the column is hidden we have to translate it + if (hbarValue + fixedColumnWidth > x) { + + tableCellX = Math.abs(hbarValue - x + fixedColumnWidth); + + n.toFront(); + fixedColumnWidth += prefWidth; + } + n.relocate(x + tableCellX, labelHeight + snappedTopInset()); + } + + x += prefWidth; + } + + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/HorizontalPicker.java b/src/impl/org/controlsfx/spreadsheet/HorizontalPicker.java new file mode 100644 index 0000000000000000000000000000000000000000..9ef50ab7ab6a1a9a38a36e1d83b20e41616a318a --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/HorizontalPicker.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.scene.control.skin.TableColumnHeader; +import java.util.Stack; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.event.EventHandler; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; +import org.controlsfx.control.spreadsheet.Picker; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * + * This class will display all the available pickers. It is a StackPane clipped + * which contain a inner Region that display the picker. In that way, we don't + * need to re-layout every time but just "slide" the inner Region inside this + * class so that the pickers are sliding along with the TableColumnHeaders. + */ +public class HorizontalPicker extends StackPane { + + private static final String PICKER_INDEX = "PickerIndex"; //$NON-NLS-1$ + + private final HorizontalHeader horizontalHeader; + + private final SpreadsheetView spv; + private final Stack<Label> pickerPile; + private final Stack<Label> pickerUsed; + + private final InnerHorizontalPicker innerPicker = new InnerHorizontalPicker(); + + public HorizontalPicker(HorizontalHeader horizontalHeader, SpreadsheetView spv) { + this.horizontalHeader = horizontalHeader; + this.spv = spv; + + pickerPile = new Stack<>(); + pickerUsed = new Stack<>(); + + //Clip this StackPane just like the TableHeaderRow. + Rectangle clip = new Rectangle(); + clip.setSmooth(true); + clip.setHeight(VerticalHeader.PICKER_SIZE); + clip.widthProperty().bind(horizontalHeader.widthProperty()); + setClip(clip); + + getChildren().add(innerPicker); + + horizontalHeader.getRootHeader().getColumnHeaders().addListener(layoutListener); + spv.getColumnPickers().addListener(layoutListener); + } + + @Override + protected void layoutChildren() { + //Just relocate the inner for sliding. + innerPicker.relocate(horizontalHeader.getRootHeader().getLayoutX(), snappedTopInset()); + //We must turn off pickers that are behind fixed columns + for (Label label : pickerUsed) { + label.setVisible(label.getLayoutX() + innerPicker.getLayoutX() + label.getWidth() > horizontalHeader.gridViewSkin.fixedColumnWidth); + } + } + + /** + * Method called by the HorizontalHeader in order to slide the pickers. + */ + public void updateScrollX() { + requestLayout(); + } + + private Label getPicker(Picker picker) { + Label pickerLabel; + if (pickerPile.isEmpty()) { + pickerLabel = new Label(); + pickerLabel.getStyleClass().addListener(layoutListener); + pickerLabel.setOnMouseClicked(pickerMouseEvent); + } else { + pickerLabel = pickerPile.pop(); + } + pickerUsed.push(pickerLabel); + pickerLabel.getStyleClass().setAll(picker.getStyleClass()); + pickerLabel.getProperties().put(PICKER_INDEX, picker); + return pickerLabel; + } + + private final EventHandler<MouseEvent> pickerMouseEvent = (MouseEvent mouseEvent) -> { + Label picker = (Label) mouseEvent.getSource(); + + ((Picker) picker.getProperties().get(PICKER_INDEX)).onClick(); + }; + + /** + * Inner class that will lay out all the pickers. + */ + private class InnerHorizontalPicker extends Region { + + @Override + protected void layoutChildren() { + pickerPile.addAll(pickerUsed.subList(0, pickerUsed.size())); + //Unbind every picker used before setting new ones. + for (Label label : pickerUsed) { + label.layoutXProperty().unbind(); + label.setVisible(true); + } + pickerUsed.clear(); + + getChildren().clear(); + int index = 0; + for (TableColumnHeader column : horizontalHeader.getRootHeader().getColumnHeaders()) { + if (spv.getColumnPickers().containsKey(index)) { + Label label = getPicker(spv.getColumnPickers().get(index)); + label.resize(column.getWidth(), VerticalHeader.PICKER_SIZE); + label.layoutXProperty().bind(column.layoutXProperty()); + + getChildren().add(0, label); + } + index++; + } + } + } + + private final InvalidationListener layoutListener = (Observable arg0) -> { + innerPicker.requestLayout(); + }; +} diff --git a/src/impl/org/controlsfx/spreadsheet/RectangleSelection.java b/src/impl/org/controlsfx/spreadsheet/RectangleSelection.java new file mode 100644 index 0000000000000000000000000000000000000000..7496914504cd850a0055a6b1ac537149faca7254 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/RectangleSelection.java @@ -0,0 +1,436 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import java.util.List; +import java.util.TreeSet; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.event.EventHandler; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.TablePosition; +import javafx.scene.input.MouseEvent; +import javafx.scene.shape.Rectangle; +import org.controlsfx.control.spreadsheet.Grid; +import org.controlsfx.control.spreadsheet.GridChange; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetColumn; + +/** + * + * This class extends Rectangle and will draw a rectangle with a border to the + * selection. + */ +public class RectangleSelection extends Rectangle { + + private final GridViewSkin skin; + private final TableViewSpanSelectionModel sm; + private final SelectionRange selectionRange; + + public RectangleSelection(GridViewSkin skin, TableViewSpanSelectionModel sm) { + this.skin = skin; + this.sm = sm; + getStyleClass().add("selection-rectangle"); //$NON-NLS-1$ + setMouseTransparent(true); + + selectionRange = new SelectionRange(); + skin.getVBar().valueProperty().addListener(layoutListener); + + //When draging, it's not working properly so we remove the rectangle. + skin.getVBar().addEventFilter(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() { + + @Override + public void handle(MouseEvent event) { + skin.getVBar().valueProperty().removeListener(layoutListener); + setVisible(false); + skin.getVBar().addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() { + + @Override + public void handle(MouseEvent event) { + skin.getVBar().removeEventFilter(MouseEvent.MOUSE_RELEASED, this); + skin.getVBar().valueProperty().addListener(layoutListener); + updateRectangle(); + } + }); + } + }); + + skin.getHBar().valueProperty().addListener(layoutListener); + sm.getSelectedCells().addListener((Observable observable) -> { + skin.getHorizontalHeader().clearSelectedColumns(); + skin.verticalHeader.clearSelectedRows(); + selectionRange.fill(sm.getSelectedCells()); + updateRectangle(); + }); + } + + private final InvalidationListener layoutListener = (Observable observable) -> { + updateRectangle(); + }; + + public final void updateRectangle() { + if (sm.getSelectedCells().isEmpty() + || skin.getSelectedRows().isEmpty() + || skin.getSelectedColumns().isEmpty() + || selectionRange.range == null) { + setVisible(false); + return; + } + + IndexedCell topRowCell = skin.getFlow().getTopRow(); + if(topRowCell == null){ + return; + } + //We fetch the first and last row currently displayed + int topRow = topRowCell.getIndex(); + IndexedCell bottomRowCell = skin.getFlow().getCells().get(skin.getFlow().getCells().size() - 1); + if(bottomRowCell == null){ + return; + } + int bottomRow = bottomRowCell.getIndex(); + + int minRow = selectionRange.range.getTop(); + if (minRow > bottomRow) { + setVisible(false); + return; + } + minRow = Math.max(minRow, topRow); + + int maxRow = selectionRange.range.getBottom(); + if (maxRow < topRow) { + setVisible(false); + return; + } + + maxRow = Math.min(maxRow, bottomRow); + int minColumn = selectionRange.range.getLeft(); + int maxColumn = selectionRange.range.getRight(); + + GridRow gridMinRow = skin.getRowIndexed(minRow); + if (gridMinRow == null) { + setVisible(false); + return; + } + + Grid grid = skin.spreadsheetView.getGrid(); + if (maxRow >= grid.getRowCount() || maxColumn >= grid.getColumnCount()) { + setVisible(false); + return; + } + SpreadsheetCell cell = grid.getRows().get(maxRow).get(maxColumn); + handleHorizontalPositioning(minColumn, maxColumn, cell.getColumnSpan()); + + //If we are out of sight + if (getX() + getWidth() < 0) { + setVisible(false); + return; + } + + GridRow gridMaxRow = skin.getRowIndexed(maxRow); + if (gridMaxRow == null) { + setVisible(false); + return; + } + setVisible(true); + + handleVerticalPositioning(minRow, maxRow, gridMinRow, gridMaxRow, cell.getRowSpan()); + } + + /** + * This will compute and assign the y and height properties of the + * rectangle. + * + * @param minRow + * @param maxRow + * @param gridMinRow + */ + private void handleVerticalPositioning(int minRow, int maxRow, GridRow gridMinRow, GridRow gridMaxRow, int rowSpan) { + double height = 0; + for (int i = maxRow; i <= maxRow /*+ (rowSpan - 1)*/; ++i) { + height += skin.getRowHeight(i); + } + + /** + * If we are not in fixed row, we will just take the layout Y, and if + * it's below some of our fixed rows, we will take the fixedRowheight as + * value. + */ + if (!skin.getCurrentlyFixedRow().contains(minRow)) { + yProperty().unbind(); + //If we have fixedRows, we do not want to overlap them with the rectangle. + if (gridMinRow.getLayoutY() < skin.getFixedRowHeight()) { + setY(skin.getFixedRowHeight()); + } else { + yProperty().bind(gridMinRow.layoutYProperty()); + } + /** + * If we are in fixedRow, we cannot trust the layoutY alone. We also + * need to rely on the verticalShift for shifting the rectangle to + * the right starting position.\n + * + */ + } else { + yProperty().bind(gridMinRow.layoutYProperty().add(gridMinRow.verticalShift)); + } + + /** + * Finally we compute the height by subtracting our starting point to + * the ending point. + */ + heightProperty().bind(gridMaxRow.layoutYProperty().add(gridMaxRow.verticalShift).subtract(yProperty()).add(height)); + } + + /** + * This will compute and assign the x and width propertis of the Rectangle. + * + * @param minColumn + * @param maxColumn + */ + private void handleHorizontalPositioning(int minColumn, int maxColumn, int columnSpan) { + double x = 0; + + final List<SpreadsheetColumn> columns = skin.spreadsheetView.getColumns(); + if(columns.size() <= minColumn || columns.size() <= maxColumn){ + return; + } + //We first compute the total space between the left edge and our first column + for (int i = 0; i < minColumn; ++i) { + //Here we use Ceil because we want to "snapSize" otherwise we may end up with a weird shift. + x += snapSize(columns.get(i).getWidth()); + } + + + /** + * We then substract the value of the Hbar in order to place it properly + * because 0 means the left edge or the SpreadsheetView and we want to + * consider the left edge of the viewport of the virtualFlow. + */ + x -= skin.getHBar().getValue(); + + //Then we compute the width by adding the space between the min and max column + double width = 0; + for (int i = minColumn; i <= maxColumn /*+ (columnSpan - 1)*/; ++i) { + width += snapSize(columns.get(i).getWidth()); + } + + //FIXED COLUMNS + /** + * If the selection is not on a fixed column, we may have the case where + * the first selected cell will be hid by a fixed column. If so, we must + * translate the starting point in because the rectangle must also be + * hidden by the fixed column. + */ + if (!skin.spreadsheetView.getFixedColumns().contains(columns.get(minColumn))) { + if (x < skin.fixedColumnWidth) { + //Since I translate the starting point, I must reduce the width by the value I'm translating. + width -= skin.fixedColumnWidth - x; + x = skin.fixedColumnWidth; + } + /** + * If the maxColumn is contained within the fixed column, we may + * look at the starting point and the ending point. Because prior + * computation are wrong since our columns are fixed on the left. So + * there initial position are worthless and we must consider their + * current position compared to each other. + * + */ + } else { + /** + * If x + width is inferior, we can re-compute the width by checking + * our fixed columns interval + */ + if (x + width < skin.fixedColumnWidth) { + x = 0; + width = 0; + for (SpreadsheetColumn column : skin.spreadsheetView.getFixedColumns()) { + int indexColumn = columns.indexOf(column); + if (indexColumn < minColumn && indexColumn != minColumn) { + x += snapSize(column.getWidth()); + } + if (indexColumn >= minColumn && indexColumn <= maxColumn) { + width += snapSize(column.getWidth()); + } + } + /** + * If just x is inferior to fixedColumnWidth, we just adjust the + * width by substracting the gap between the original x and the + * new x. + */ + } else if (x < skin.fixedColumnWidth) { + double tempX = 0; + for (SpreadsheetColumn column : skin.spreadsheetView.getFixedColumns()) { + int indexColumn = columns.indexOf(column); + if (indexColumn < minColumn && indexColumn != minColumn) { + tempX += snapSize(column.getWidth()); + } + } + width -= tempX - x; + x = tempX; + } + } + setX(x); + setWidth(width); + } + + /** + * Returns a value ceiled to the nearest pixel. + * + * @param value the size value to be snapped + * @return value ceiled to nearest pixel + */ + private double snapSize(double value) { + return Math.ceil(value); + } + + /** + * Utility class to transform a list of selected cells into a union of + * ranges. + */ + public static class SelectionRange { + + private final TreeSet<Long> set = new TreeSet<>(); + private GridRange range; + + public SelectionRange() { + } + + /** + * Construct a SelectionRange with a List of Pair where the value is the + * row (of the WsGrid) and the value is column(of the WsGrid). + * + * @param list + */ + public void fill(List<TablePosition> list) { + set.clear(); + for (TablePosition pos : list) { + set.add(key(pos.getRow(), pos.getColumn())); + } + computeRange(); + } + + public void fillGridRange(List<GridChange> list) { + set.clear(); + for (GridChange pos : list) { + set.add(key(pos.getRow(), pos.getColumn())); + } + computeRange(); + } + + public GridRange getRange(){ + return range; + } + private Long key(int row, int column) { + return (((long) row) << 32) | column; + } + + private int getRow(Long l) { + return (int) (l >> 32); + } + + private int getColumn(Long l) { + return (int) (l & 0xffFFffFF); + } + + /** + * return a list of WsGridRange + * + * @return + */ + private void computeRange() { + range = null; + while (!set.isEmpty()) { + if (range != null) { + range = null; + return; + } + + long first = set.first(); + set.remove(first); + + int row = getRow(first); + int column = getColumn(first); + + //Go in row + while (set.contains(key(row, column + 1))) { + ++column; + set.remove(key(row, column)); + } + + //Go in column + boolean flag = true; + while (flag) { + ++row; + for (int col = getColumn(first); col <= column; ++col) { + if (!set.contains(key(row, col))) { + flag = false; + break; + } + } + if (flag) { + for (int col = getColumn(first); col <= column; ++col) { + set.remove(key(row, col)); + } + } else { + --row; + } + } + range = new GridRange(getRow(first), row, getColumn(first), column); + } + } + } + + public static class GridRange { + + private final int top; + private final int bottom; + private final int left; + private final int right; + + public GridRange(int top, int bottom, int left, int right) { + this.top = top; + this.bottom = bottom; + this.left = left; + this.right = right; + } + + public int getTop() { + return top; + } + + public int getBottom() { + return bottom; + } + + public int getLeft() { + return left; + } + + public int getRight() { + return right; + } + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/SelectedCellsMapTemp.java b/src/impl/org/controlsfx/spreadsheet/SelectedCellsMapTemp.java new file mode 100644 index 0000000000000000000000000000000000000000..9d37392fdabe5499aa707e8ec12093639f5a9268 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/SelectedCellsMapTemp.java @@ -0,0 +1,205 @@ +/** + * Copyright (c) 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import java.util.BitSet; +import java.util.Collection; +import java.util.Map; +import java.util.TreeMap; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.scene.control.TablePositionBase; + +/** + * This class is copied from com.sun.javafx.scene.control.SelectedCellsMap + * temporary in 8u20 to resolve https://javafx-jira.kenai.com/browse/RT-38306 + * + * Will be removed in 8u40 + * + * @param <T> + */ +public class SelectedCellsMapTemp<T extends TablePositionBase> { + private final ObservableList<T> selectedCells; + private final ObservableList<T> sortedSelectedCells; + + private final Map<Integer, BitSet> selectedCellBitSetMap; + + public SelectedCellsMapTemp(final ListChangeListener<T> listener) { + selectedCells = FXCollections.<T>observableArrayList(); + sortedSelectedCells = new SortedList<>(selectedCells, (T o1, T o2) -> { + int result = o1.getRow() - o2.getRow(); + return result == 0 ? (o1.getColumn() - o2.getColumn()) : result; + }); + sortedSelectedCells.addListener(listener); + + selectedCellBitSetMap = new TreeMap<>((o1, o2) -> o1.compareTo(o2)); + } + + public int size() { + return selectedCells.size(); + } + + public T get(int i) { + if (i < 0) { + return null; + } + return sortedSelectedCells.get(i); + } + + public void add(T tp) { + final int row = tp.getRow(); + final int columnIndex = tp.getColumn(); + + // update the bitset map + BitSet bitset; + if (! selectedCellBitSetMap.containsKey(row)) { + bitset = new BitSet(); + selectedCellBitSetMap.put(row, bitset); + } else { + bitset = selectedCellBitSetMap.get(row); + } + + if (columnIndex >= 0) { + boolean isAlreadySet = bitset.get(columnIndex); + bitset.set(columnIndex); + + if (! isAlreadySet) { + // add into the list + selectedCells.add(tp); + } + } else { + // FIXME slow path (for now) + if (! selectedCells.contains(tp)) { + selectedCells.add(tp); + } + } + } + + public void addAll(Collection<T> cells) { + // update bitset + for (T tp : cells) { + final int row = tp.getRow(); + final int columnIndex = tp.getColumn(); + + // update the bitset map + BitSet bitset; + if (! selectedCellBitSetMap.containsKey(row)) { + bitset = new BitSet(); + selectedCellBitSetMap.put(row, bitset); + } else { + bitset = selectedCellBitSetMap.get(row); + } + + if (columnIndex < 0) { + continue; + } + + bitset.set(columnIndex); + } + + // add into the list + selectedCells.addAll(cells); + } + + public void setAll(Collection<T> cells) { + // update bitset + selectedCellBitSetMap.clear(); + for (T tp : cells) { + final int row = tp.getRow(); + final int columnIndex = tp.getColumn(); + + // update the bitset map + BitSet bitset; + if (! selectedCellBitSetMap.containsKey(row)) { + bitset = new BitSet(); + selectedCellBitSetMap.put(row, bitset); + } else { + bitset = selectedCellBitSetMap.get(row); + } + + if (columnIndex < 0) { + continue; + } + + bitset.set(columnIndex); + } + + // add into the list + selectedCells.setAll(cells); + } + + public void remove(T tp) { + final int row = tp.getRow(); + final int columnIndex = tp.getColumn(); + + // update the bitset map + if (selectedCellBitSetMap.containsKey(row)) { + BitSet bitset = selectedCellBitSetMap.get(row); + + if (columnIndex >= 0) { + bitset.clear(columnIndex); + } + + if (bitset.isEmpty()) { + selectedCellBitSetMap.remove(row); + } + } + + // update list + selectedCells.remove(tp); + } + + public void clear() { + // update bitset + selectedCellBitSetMap.clear(); + + // update list + selectedCells.clear(); + } + + public boolean isSelected(int row, int columnIndex) { + if (columnIndex < 0) { + return selectedCellBitSetMap.containsKey(row); + } else { + return selectedCellBitSetMap.containsKey(row) ? selectedCellBitSetMap.get(row).get(columnIndex) : false; + } + } + + public int indexOf(T tp) { + return sortedSelectedCells.indexOf(tp); + } + + public boolean isEmpty() { + return selectedCells.isEmpty(); + } + + public ObservableList<T> getSelectedCells() { + return selectedCells; + } +} diff --git a/src/impl/org/controlsfx/spreadsheet/SpreadsheetGridView.java b/src/impl/org/controlsfx/spreadsheet/SpreadsheetGridView.java new file mode 100644 index 0000000000000000000000000000000000000000..53f9fe9119138623e45a53968a793dbf5676fdb6 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/SpreadsheetGridView.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2013, 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.scene.control.TableView; +import javafx.scene.input.MouseEvent; + +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +public class SpreadsheetGridView extends TableView<ObservableList<SpreadsheetCell>> { + private final SpreadsheetHandle handle; + + /* + * cache the stylesheet as lookup takes time and the getUserAgentStylesheet is called repeatedly + */ + private String stylesheet; + + /** + * We don't want to show the current value in the TextField when we are + * editing by typing a key. We want directly to take those typed letters + * and put them into the textfield. + */ + public SpreadsheetGridView(SpreadsheetHandle handle) { + this.handle = handle; + + + } + + @Override + public String getUserAgentStylesheet() { + /* + * For more information please see RT-40658 + */ + if (stylesheet == null) { + stylesheet = SpreadsheetView.class.getResource("spreadsheet.css") //$NON-NLS-1$ + .toExternalForm(); + } + + return stylesheet; + } + + @Override + protected javafx.scene.control.Skin<?> createDefaultSkin() { + return new GridViewSkin(handle); + } + + public GridViewSkin getGridViewSkin() { + return handle.getCellsViewSkin(); + } +}; diff --git a/src/impl/org/controlsfx/spreadsheet/SpreadsheetHandle.java b/src/impl/org/controlsfx/spreadsheet/SpreadsheetHandle.java new file mode 100644 index 0000000000000000000000000000000000000000..474f0090ce9aeeecdd2f3c6384e4c1f18aaaf5ab --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/SpreadsheetHandle.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package impl.org.controlsfx.spreadsheet; + +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * Implementation vs public bridge. + */ +public abstract class SpreadsheetHandle { + /** Access the main control. */ + protected abstract SpreadsheetView getView(); + /** Accesses the grid (ie cell table) in the spreadsheet. */ + protected abstract SpreadsheetGridView getGridView(); + /** Accesses the grid view (ie cell table view). */ + protected abstract GridViewSkin getCellsViewSkin(); + /** Whether that column width has been set by the user. */ + protected abstract boolean isColumnWidthSet(int indexColumn); +} diff --git a/src/impl/org/controlsfx/spreadsheet/TableViewSpanSelectionModel.java b/src/impl/org/controlsfx/spreadsheet/TableViewSpanSelectionModel.java new file mode 100644 index 0000000000000000000000000000000000000000..c2b585bae163cd04d0a5343c196b25f6a6b3fbee --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/TableViewSpanSelectionModel.java @@ -0,0 +1,912 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import com.sun.javafx.collections.MappingChange; +import com.sun.javafx.collections.NonIterableChange; +import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.beans.InvalidationListener; +import javafx.beans.NamedArg; +import javafx.beans.Observable; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.WeakListChangeListener; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.event.WeakEventHandler; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumnBase; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.util.Duration; +import javafx.util.Pair; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * + * The Selection Model adapted for the SpreadsheetView regarding span. + */ +public class TableViewSpanSelectionModel extends + TableView.TableViewSelectionModel<ObservableList<SpreadsheetCell>>{ + + private boolean shift = false; // Register state of 'shift' key + private boolean key = false; // Register if we last touch the keyboard + // or the mouse + private boolean drag = false; // register if we are dragging (no + // edition) + private MouseEvent mouseEvent; + private boolean makeAtomic; + private SpreadsheetGridView cellsView; + + private SpreadsheetView spreadsheetView; + // the only 'proper' internal data structure, selectedItems and + // selectedIndices + // are both 'read-only and unbacked'. + private final SelectedCellsMapTemp<TablePosition<ObservableList<SpreadsheetCell>, ?>> selectedCellsMap; + + // we create a ReadOnlyUnbackedObservableList of selectedCells here so + // that we can fire custom list change events. + private final ReadOnlyUnbackedObservableList<TablePosition<ObservableList<SpreadsheetCell>, ?>> selectedCellsSeq; + + /** + * We use these variable in order to stay on the same row/column when + * navigating with arrows. If we are going down, and we are arriving on a + * column-spanning cell, when going down again, we don't want to go on the + * starting column of the spanning cell but on the same column we arrived + * previously. + */ + private int oldCol = -1; + private TableColumn oldTableColumn = null; + private int oldRow = -1; + Pair<Integer, Integer> direction; + private int oldColSpan = -1; + private int oldRowSpan = -1; + /** + * Make the tableView move when selection operating outside bounds + */ + private final Timeline timer; + + private final EventHandler<ActionEvent> timerEventHandler = (ActionEvent event) -> { + GridViewSkin skin = (GridViewSkin) getCellsViewSkin(); + if (mouseEvent != null && !cellsView.contains(mouseEvent.getX(), mouseEvent.getY())) { + double sceneX = mouseEvent.getSceneX(); + double sceneY = mouseEvent.getSceneY(); + double layoutX = cellsView.getLayoutX(); + double layoutY = cellsView.getLayoutY(); + double layoutXMax = layoutX + cellsView.getWidth(); + double layoutYMax = layoutY + cellsView.getHeight(); + + if (sceneX > layoutXMax) { + skin.getHBar().increment(); + } else if (sceneX < layoutX) { + skin.getHBar().decrement(); + } + if (sceneY > layoutYMax) { + skin.getVBar().increment(); + } else if (sceneY < layoutY) { + skin.getVBar().decrement(); + } + } + }; + /** + * When the drag is over, we remove the listener and stop the timer + */ + private final EventHandler<MouseEvent> dragDoneHandler = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent mouseEvent) { + drag = false; + timer.stop(); + spreadsheetView.removeEventHandler(MouseEvent.MOUSE_RELEASED, this); + } + }; + + private final EventHandler<KeyEvent> keyPressedEventHandler = (KeyEvent keyEvent) -> { + key = true; + shift = keyEvent.isShiftDown(); + }; + + private final EventHandler<MouseEvent> mousePressedEventHandler = (MouseEvent mouseEvent1) -> { + key = false; + shift = mouseEvent1.isShiftDown(); + }; + + private final EventHandler<MouseEvent> onDragDetectedEventHandler = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent mouseEvent) { + cellsView.addEventHandler(MouseEvent.MOUSE_RELEASED, dragDoneHandler); + drag = true; + timer.setCycleCount(Timeline.INDEFINITE); + timer.play(); + } + }; + + private final EventHandler<MouseEvent> onMouseDragEventHandler = (MouseEvent e) -> { + mouseEvent = e; + }; + + private final ListChangeListener<TablePosition<ObservableList<SpreadsheetCell>, ?>> listChangeListener = this::handleSelectedCellsListChangeEvent; + + /** + * ********************************************************************* + * + * Constructors + * + ********************************************************************* + */ + /** + * Constructor + * @param spreadsheetView + * @param cellsView + */ + public TableViewSpanSelectionModel(@NamedArg("spreadsheetView") SpreadsheetView spreadsheetView, @NamedArg("cellsView") SpreadsheetGridView cellsView) { + super(cellsView); + this.cellsView = cellsView; + this.spreadsheetView = spreadsheetView; + + timer = new Timeline(new KeyFrame(Duration.millis(100), new WeakEventHandler<>((timerEventHandler)))); + cellsView.addEventHandler(KeyEvent.KEY_PRESSED, new WeakEventHandler<>(keyPressedEventHandler)); + + cellsView.addEventFilter(MouseEvent.MOUSE_PRESSED, new WeakEventHandler<>(mousePressedEventHandler)); + cellsView.setOnDragDetected(new WeakEventHandler<>(onDragDetectedEventHandler)); + + cellsView.setOnMouseDragged(new WeakEventHandler<>(onMouseDragEventHandler)); + + selectedCellsMap = new SelectedCellsMapTemp<>(new WeakListChangeListener<>(listChangeListener)); + + selectedCellsSeq = new ReadOnlyUnbackedObservableList<TablePosition<ObservableList<SpreadsheetCell>, ?>>() { + @Override + public TablePosition<ObservableList<SpreadsheetCell>, ?> get(int i) { + return selectedCellsMap.get(i); + } + + @Override + public int size() { + return selectedCellsMap.size(); + } + }; + } + + private void handleSelectedCellsListChangeEvent( + ListChangeListener.Change<? extends TablePosition<ObservableList<SpreadsheetCell>, ?>> c) { + if (makeAtomic) { + return; + } + + selectedCellsSeq.callObservers(new MappingChange<>(c, MappingChange.NOOP_MAP, selectedCellsSeq)); + c.reset(); + } + + /** + * ********************************************************************* + * * Public selection API * * + * ******************************************************************** + */ + private TablePosition<ObservableList<SpreadsheetCell>, ?> old = null; + + @Override + public void select(int row, TableColumn<ObservableList<SpreadsheetCell>, ?> column) { + if (row < 0 || row >= getItemCount()) { + return; + } + + // if I'm in cell selection mode but the column is null, I don't + // want + // to select the whole row instead... + if (isCellSelectionEnabled() && column == null) { + return; + } + // Variable we need for algorithm + TablePosition<ObservableList<SpreadsheetCell>, ?> posFinal = new TablePosition<>(getTableView(), row, + column); + + final SpreadsheetView.SpanType spanType = spreadsheetView.getSpanType(row, posFinal.getColumn()); + + /** + * We check if we are on covered cell. If so we have the algorithm of + * the focus model to give the selection to the right cell. + * + */ + switch (spanType) { + case ROW_SPAN_INVISIBLE: + /** + * If we notice that the new selected cell is the previous one, + * then it means that we were already on the cell and we wanted + * to go below. We make sure that old is not null, and that the + * move is initiated by keyboard. Because if it's a click, then + * we just want to go on the clicked cell (not below) + */ + if (old != null && !shift && old.getColumn() == posFinal.getColumn() + && old.getRow() == posFinal.getRow() - 1) { + int visibleRow = FocusModelListener.getNextRowNumber(old, cellsView); + /** + * If the visibleRow we're targeting is out of bounds, we do + * not want to get a visibleCell, so we step out. But we + * need to set edition to false because we will be going + * back to the old cell and we could go to edition. + */ + if (visibleRow < getItemCount()) { + posFinal = getVisibleCell(visibleRow, old.getTableColumn(), old.getColumn()); + break; + } + } + // If the current selected cell if hidden by row span, we go + // above + posFinal = getVisibleCell(row, column, posFinal.getColumn()); + break; + case BOTH_INVISIBLE: + // If the current selected cell if hidden by a both (row and + // column) span, we go left-above + posFinal = getVisibleCell(row, column, posFinal.getColumn()); + break; + case COLUMN_SPAN_INVISIBLE: + // If we notice that the new selected cell is the previous one, + // then it means that we were + // already on the cell and we wanted to go right. + if (old != null && !shift && old.getColumn() == posFinal.getColumn() - 1 + && old.getRow() == posFinal.getRow()) { + posFinal = getVisibleCell(old.getRow(), FocusModelListener.getTableColumnSpan(old, cellsView), getTableColumnSpanInt(old)); + } else { + // If the current selected cell if hidden by column span, we + // go left + posFinal = getVisibleCell(row, column, posFinal.getColumn()); + } + default: + break; + } + + if (direction != null && key) { + /** + * If I'm going up or down, and the previous cell had a column span, + * then we take the column used before instead of the current + * column. + */ + if (direction.getKey() != 0 && oldColSpan > 1) { + posFinal = getVisibleCell(posFinal.getRow(), oldTableColumn, oldCol); + } else if (direction.getValue() != 0 && oldRowSpan > 1) { + posFinal = getVisibleCell(oldRow, posFinal.getTableColumn(), posFinal.getColumn()); + } + } + old = posFinal; + + //If it's a click, we register everything. + if (!key) { + oldRow = old.getRow(); + oldCol = old.getColumn(); + oldTableColumn = old.getTableColumn(); + } else { + //If we're going up or down, we register the row changing, not the column. + if (direction != null && direction.getKey() != 0) { + oldRow = old.getRow(); + } else if (direction != null && direction.getValue() != 0) { + oldCol = old.getColumn(); + oldTableColumn = old.getTableColumn(); + } + } + if (getSelectionMode() == SelectionMode.SINGLE) { + quietClearSelection(); + } + SpreadsheetCell cell = cellsView.getItems().get(old.getRow()).get(old.getColumn()); + oldRowSpan = cell.getRowSpan(); + oldColSpan = cell.getColumnSpan(); + for (int i = cell.getRow(); i < cell.getRowSpan() + cell.getRow(); ++i) { + for (int j = cell.getColumn(); j < cell.getColumnSpan() + cell.getColumn(); ++j) { + posFinal = new TablePosition<>(getTableView(), i, getTableView().getVisibleLeafColumn(j)); + selectedCellsMap.add(posFinal); + } + } + + updateScroll(old); + addSelectedRowsAndColumns(old); + + setSelectedIndex(old.getRow()); + setSelectedItem(getModelItem(old.getRow())); + if (getTableView().getFocusModel() == null) { + return; + } + + getTableView().getFocusModel().focus(old.getRow(), old.getTableColumn()); + } + + /** + * We try to make visible the rows that may be hidden by Fixed rows. + * + * @param posFinal + */ + private void updateScroll(TablePosition<ObservableList<SpreadsheetCell>, ?> posFinal) { + + /** + * We don't want to do any scroll when dragging or selecting with click. + * Only keyboard action arrow action. + */ + if (!drag && key && getCellsViewSkin().getCellsSize() != 0 && spreadsheetView.getFixedRows().size() != 0) { + + int start = getCellsViewSkin().getRow(0).getIndex(); + double posFinalOffset = 0; + for (int j = start; j < posFinal.getRow(); ++j) { + posFinalOffset += getSpreadsheetViewSkin().getRowHeight(j); + } + + if (getCellsViewSkin().getFixedRowHeight() > posFinalOffset) { + cellsView.scrollTo(posFinal.getRow()); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void clearSelection(int row, TableColumn<ObservableList<SpreadsheetCell>, ?> column) { + + final TablePosition<ObservableList<SpreadsheetCell>, ?> tp = new TablePosition<>(getTableView(), row, + column); + if (tp.getRow() < 0 || tp.getColumn() < 0) { + return; + } + List<TablePosition<ObservableList<SpreadsheetCell>, ?>> selectedCells; + if ((selectedCells = isSelectedRange(row, column, tp.getColumn())) != null) { + for (TablePosition<ObservableList<SpreadsheetCell>, ?> cell : selectedCells) { + selectedCellsMap.remove(cell); + removeSelectedRowsAndColumns(cell); + focus(cell.getRow()); + } + } else { + for (TablePosition<ObservableList<SpreadsheetCell>, ?> pos : getSelectedCells()) { + if (pos.equals(tp)) { + selectedCellsMap.remove(pos); + removeSelectedRowsAndColumns(pos); + // give focus to this cell index + focus(row); + return; + } + } + } + } + + /** + * When we set a new grid, we need to update the selected Cells because + * otherwise we will end up with TablePosition which have "-1" as their + * column number. So we need to verify that the old selected cells are still + * selectable and select them. + * + * @param selectedCells + */ + public void verifySelectedCells(List<Pair<Integer, Integer>> selectedCells) { + List<TablePosition<ObservableList<SpreadsheetCell>, ?>> newList = new ArrayList<>(); + clearSelection(); + + final int itemCount = getItemCount(); + final int columnSize = getTableView().getColumns().size(); + final HashSet<Integer> selectedRows = new HashSet<>(); + final HashSet<Integer> selectedColumns = new HashSet<>(); + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = null; + for (Pair<Integer, Integer> position : selectedCells) { + if (position.getKey() < 0 + || position.getKey() >= itemCount + || position.getValue() < 0 + || position.getValue() >= columnSize) { + continue; + } + + final TableColumn<ObservableList<SpreadsheetCell>, ?> column = getTableView().getVisibleLeafColumn(position.getValue()); + + pos = getVisibleCell(position.getKey(), column, position.getValue()); + // We store all the selectedColumn and Rows, we will update + // just once at the end + final SpreadsheetCell cell = cellsView.getItems().get(pos.getRow()).get(pos.getColumn()); + for (int i = cell.getRow(); i < cell.getRowSpan() + cell.getRow(); ++i) { + selectedColumns.add(i); + for (int j = cell.getColumn(); j < cell.getColumnSpan() + cell.getColumn(); ++j) { + selectedRows.add(j); + pos = new TablePosition<>(getTableView(), i, getTableView().getVisibleLeafColumn(j)); + newList.add(pos); + } + } + } + selectedCellsMap.setAll(newList); + + final TablePosition finalPos = pos; + // Then we update visuals just once + GridViewSkin skin = getSpreadsheetViewSkin(); + //If the skin is null, we just wait till everything is ready.. + if (skin == null) { + cellsView.skinProperty().addListener(new InvalidationListener() { + + @Override + public void invalidated(Observable observable) { + cellsView.skinProperty().removeListener(this); + GridViewSkin skin = getSpreadsheetViewSkin(); + if (skin != null) { + updateSelectedVisuals(skin, finalPos, selectedRows, selectedColumns); + } + } + }); + } else { + updateSelectedVisuals(skin, pos, selectedRows, selectedColumns); + } + } + + /** + * When all the selection has been made, we just need to light up the + * indicators that are showing which indexes are selected. + * + * @param skin + * @param pos + * @param selectedRows + * @param selectedColumns + */ + private void updateSelectedVisuals(GridViewSkin skin, TablePosition pos, HashSet<Integer> selectedRows, HashSet<Integer> selectedColumns) { + if (skin != null) { + skin.getSelectedRows().addAll(selectedColumns); + skin.getSelectedColumns().addAll(selectedRows); + } + + /** + * If we made some selection, we need to force the visual selected + * confirmation to come when the layout is starting. Doing it before + * will result in a selected cell with no css applied to it. + */ + if (pos != null) { + getCellsViewSkin().lastRowLayout.set(true); + getCellsViewSkin().lastRowLayout.addListener(new InvalidationListener() { + + @Override + public void invalidated(Observable observable) { + handleSelectedCellsListChangeEvent(new NonIterableChange.SimpleAddChange<>(0, + selectedCellsMap.size(), selectedCellsSeq)); + getCellsViewSkin().lastRowLayout.removeListener(this); + } + }); + } + } + + @Override + public void selectRange(int minRow, TableColumnBase<ObservableList<SpreadsheetCell>, ?> minColumn, int maxRow, + TableColumnBase<ObservableList<SpreadsheetCell>, ?> maxColumn) { + + if (getSelectionMode() == SelectionMode.SINGLE) { + quietClearSelection(); + select(maxRow, maxColumn); + return; + } + SpreadsheetCell cell; + + makeAtomic = true; + + final int itemCount = getItemCount(); + + final int minColumnIndex = getTableView().getVisibleLeafIndex( + (TableColumn<ObservableList<SpreadsheetCell>, ?>) minColumn); + final int maxColumnIndex = getTableView().getVisibleLeafIndex( + (TableColumn<ObservableList<SpreadsheetCell>, ?>) maxColumn); + final int _minColumnIndex = Math.min(minColumnIndex, maxColumnIndex); + final int _maxColumnIndex = Math.max(minColumnIndex, maxColumnIndex); + + final int _minRow = Math.min(minRow, maxRow); + final int _maxRow = Math.max(minRow, maxRow); + + HashSet<Integer> selectedRows = new HashSet<>(); + HashSet<Integer> selectedColumns = new HashSet<>(); + + for (int _row = _minRow; _row <= _maxRow; _row++) { + for (int _col = _minColumnIndex; _col <= _maxColumnIndex; _col++) { + // begin copy/paste of select(int, column) method (with some + // slight modifications) + if (_row < 0 || _row >= itemCount) { + continue; + } + + final TableColumn<ObservableList<SpreadsheetCell>, ?> column = getTableView().getVisibleLeafColumn( + _col); + + // if I'm in cell selection mode but the column is null, I + // don't want + // to select the whole row instead... + if (column == null) { + continue; + } + + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = getVisibleCell(_row, column, _col); + + // We store all the selectedColumn and Rows, we will update + // just once at the end + cell = cellsView.getItems().get(pos.getRow()).get(pos.getColumn()); + for (int i = cell.getRow(); i < cell.getRowSpan() + cell.getRow(); ++i) { + selectedColumns.add(i); + for (int j = cell.getColumn(); j < cell.getColumnSpan() + cell.getColumn(); ++j) { + selectedRows.add(j); + pos = new TablePosition<>(getTableView(), i, getTableView().getVisibleLeafColumn(j)); + selectedCellsMap.add(pos); + } + } + +// makeAtomic = true; + // end copy/paste + } + } + makeAtomic = false; + + // Then we update visuals just once + getSpreadsheetViewSkin().getSelectedRows().addAll(selectedColumns); + getSpreadsheetViewSkin().getSelectedColumns().addAll(selectedRows); + + // fire off events + setSelectedIndex(maxRow); + setSelectedItem(getModelItem(maxRow)); + if (getTableView().getFocusModel() == null) { + return; + } + + //FIXME Focus is wrong, and endIndex also.. + getTableView().getFocusModel().focus(maxRow, (TableColumn<ObservableList<SpreadsheetCell>, ?>) maxColumn); + + /** + * If we end up on a spanned cell, there is not reliable way to + * determine which is the last index, certainly not the maxRow and + * maxColumn. So right now we need to take this extreme measure in order + * to be sure that the cells will be highlighted correctly. + */ + final int startChangeIndex = selectedCellsMap.indexOf(new TablePosition<>(getTableView(), minRow, + (TableColumn<ObservableList<SpreadsheetCell>, ?>) minColumn)); + final int endChangeIndex = selectedCellsMap.getSelectedCells().size() - 1;//indexOf(new TablePosition<>(getTableView(), maxRow, +// (TableColumn<ObservableList<SpreadsheetCell>, ?>) maxColumn)); + + if (startChangeIndex > -1 && endChangeIndex > -1) { + final int startIndex = Math.min(startChangeIndex, endChangeIndex); + final int endIndex = Math.max(startChangeIndex, endChangeIndex); + handleSelectedCellsListChangeEvent(new NonIterableChange.SimpleAddChange<>(startIndex, + endIndex + 1, selectedCellsSeq)); + } + } + + @Override + public void selectAll() { + if (getSelectionMode() == SelectionMode.SINGLE) { + return; + } + + quietClearSelection(); + + List<TablePosition<ObservableList<SpreadsheetCell>, ?>> indices = new ArrayList<>(); + TableColumn<ObservableList<SpreadsheetCell>, ?> column; + TablePosition<ObservableList<SpreadsheetCell>, ?> tp = null; + + for (int col = 0; col < getTableView().getVisibleLeafColumns().size(); col++) { + column = getTableView().getVisibleLeafColumns().get(col); + for (int row = 0; row < getItemCount(); row++) { + tp = new TablePosition<>(getTableView(), row, column); + indices.add(tp); + } + } + selectedCellsMap.setAll(indices); + + // Then we update visuals just once + ArrayList<Integer> selectedColumns = new ArrayList<>(); + for (int col = 0; col < spreadsheetView.getGrid().getColumnCount(); col++) { + selectedColumns.add(col); + } + + ArrayList<Integer> selectedRows = new ArrayList<>(); + for (int row = 0; row < spreadsheetView.getGrid().getRowCount(); row++) { + selectedRows.add(row); + } + getSpreadsheetViewSkin().getSelectedRows().addAll(selectedRows); + getSpreadsheetViewSkin().getSelectedColumns().addAll(selectedColumns); + + if (tp != null) { + select(tp.getRow(), tp.getTableColumn()); + //Just like verticalHeader, the focus should be put on the + //first cell to ease copy/paste operation. + getTableView().getFocusModel().focus(0, getTableView().getColumns().get(0)); + } + } + + @Override + public boolean isSelected(int row, TableColumn<ObservableList<SpreadsheetCell>, ?> column) { + // When in cell selection mode, we currently do NOT support + // selecting + // entire rows, so a isSelected(row, null) + // should always return false. + if (column == null || row < 0) { + return false; + } + + int columnIndex = getTableView().getVisibleLeafIndex(column); + + if (getCellsViewSkin().getCellsSize() != 0) { + TablePosition<ObservableList<SpreadsheetCell>, ?> posFinal = getVisibleCell(row, column, columnIndex); + return selectedCellsMap.isSelected(posFinal.getRow(), posFinal.getColumn()); + } else { + return selectedCellsMap.isSelected(row, columnIndex); + } + } + + /** + * Return the tablePosition of a selected cell inside a spanned cell if any. + * + * @param row + * @param column + * @param col + * @return + */ + public List<TablePosition<ObservableList<SpreadsheetCell>, ?>> isSelectedRange(int row, + TableColumn<ObservableList<SpreadsheetCell>, ?> column, int col) { + + if (col < 0 || row < 0) { + return null; + } + + final SpreadsheetCell cellSpan = cellsView.getItems().get(row).get(col); + final int infRow = cellSpan.getRow(); + final int supRow = infRow + cellSpan.getRowSpan(); + + final int infCol = cellSpan.getColumn(); + final int supCol = infCol + cellSpan.getColumnSpan(); + List<TablePosition<ObservableList<SpreadsheetCell>, ?>> selectedCells = new ArrayList<>(); + for (final TablePosition<ObservableList<SpreadsheetCell>, ?> tp : getSelectedCells()) { + if (tp.getRow() >= infRow && tp.getRow() < supRow && tp.getColumn() >= infCol + && tp.getColumn() < supCol) { + selectedCells.add(tp); + } + } + return selectedCells.isEmpty()? null : selectedCells; + } + + /** + * ********************************************************************* + * * Support code * * + * ******************************************************************** + */ + private void addSelectedRowsAndColumns(TablePosition<?, ?> position) { + GridViewSkin skin = getSpreadsheetViewSkin(); + if (skin == null) { + return; + } + final SpreadsheetCell cell = cellsView.getItems().get(position.getRow()).get(position.getColumn()); + for (int i = cell.getRow(); i < cell.getRowSpan() + cell.getRow(); ++i) { + skin.getSelectedRows().add(i); + for (int j = cell.getColumn(); j < cell.getColumnSpan() + cell.getColumn(); ++j) { + skin.getSelectedColumns().add(j); + } + } + } + + private void removeSelectedRowsAndColumns(TablePosition<?, ?> position) { + final SpreadsheetCell cell = cellsView.getItems().get(position.getRow()).get(position.getColumn()); + for (int i = cell.getRow(); i < cell.getRowSpan() + cell.getRow(); ++i) { + getSpreadsheetViewSkin().getSelectedRows().remove(Integer.valueOf(i)); + for (int j = cell.getColumn(); j < cell.getColumnSpan() + cell.getColumn(); ++j) { + getSpreadsheetViewSkin().getSelectedColumns().remove(Integer.valueOf(j)); + } + } + } + + @Override + public void clearAndSelect(int row, TableColumn<ObservableList<SpreadsheetCell>, ?> column) { + // RT-33558 if this method has been called with a given row/column + // intersection, and that row/column intersection is the only + // selection currently, then this method becomes a no-op. + + // This is understandable but not compatible with spanning + // selection. + /* + * if (getSelectedCells().size() == 1 && isSelected(row, column)) { + * return; } + */ + makeAtomic = true; + // firstly we make a copy of the selection, so that we can send out + // the correct details in the selection change event + List<TablePosition<ObservableList<SpreadsheetCell>, ?>> previousSelection = new ArrayList<>( + selectedCellsMap.getSelectedCells()); + + // then clear the current selection + clearSelection(); + + // and select the new row + select(row, column); + + makeAtomic = false; + + // fire off a single add/remove/replace notification (rather than + // individual remove and add notifications) - see RT-33324 + if (old != null && old.getColumn() >= 0) { + TableColumn<ObservableList<SpreadsheetCell>, ?> columnFinal = getTableView().getColumns().get( + old.getColumn()); + int changeIndex = selectedCellsSeq.indexOf(new TablePosition<>(getTableView(), old.getRow(), + columnFinal)); + NonIterableChange.GenericAddRemoveChange<TablePosition<ObservableList<SpreadsheetCell>, ?>> change = new NonIterableChange.GenericAddRemoveChange<>( + changeIndex, changeIndex + 1, previousSelection, selectedCellsSeq); + handleSelectedCellsListChangeEvent(change); + } + } + + /** + * FIXME I don't understand why TablePosition is not parameterized in the + * API.. + * + * @return + */ + @Override + public ObservableList<TablePosition> getSelectedCells() { + return (ObservableList<TablePosition>) (Object) selectedCellsSeq; + } + + @Override + public void selectAboveCell() { + final TablePosition<ObservableList<SpreadsheetCell>, ?> pos = getFocusedCell(); + if (pos.getRow() == -1) { + select(getItemCount() - 1); + } else if (pos.getRow() > 0) { + select(pos.getRow() - 1, pos.getTableColumn()); + } + + } + + @Override + public void selectBelowCell() { + final TablePosition<ObservableList<SpreadsheetCell>, ?> pos = getFocusedCell(); + + if (pos.getRow() == -1) { + select(0); + } else if (pos.getRow() < getItemCount() - 1) { + select(pos.getRow() + 1, pos.getTableColumn()); + } + + } + + @Override + public void selectLeftCell() { + if (!isCellSelectionEnabled()) { + return; + } + + final TablePosition<ObservableList<SpreadsheetCell>, ?> pos = getFocusedCell(); + if (pos.getColumn() - 1 >= 0) { + select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1)); + } + + } + + @Override + public void selectRightCell() { + if (!isCellSelectionEnabled()) { + return; + } + + final TablePosition<ObservableList<SpreadsheetCell>, ?> pos = getFocusedCell(); + if (pos.getColumn() + 1 < getTableView().getVisibleLeafColumns().size()) { + select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1)); + } + + } + + @Override + public void clearSelection() { + if (!makeAtomic) { + setSelectedIndex(-1); + setSelectedItem(getModelItem(-1)); + focus(-1); + } + quietClearSelection(); + } + + private void quietClearSelection() { + selectedCellsMap.clear(); + GridViewSkin skin = getSpreadsheetViewSkin(); + if (skin != null) { + skin.getSelectedRows().clear(); + skin.getSelectedColumns().clear(); + } + } + + @SuppressWarnings("unchecked") + private TablePosition<ObservableList<SpreadsheetCell>, ?> getFocusedCell() { + if (getTableView().getFocusModel() == null) { + return new TablePosition<>(getTableView(), -1, null); + } + return (TablePosition<ObservableList<SpreadsheetCell>, ?>) cellsView.getFocusModel().getFocusedCell(); + } + + private TableColumn<ObservableList<SpreadsheetCell>, ?> getTableColumn( + TableColumn<ObservableList<SpreadsheetCell>, ?> column, int offset) { + final int columnIndex = getTableView().getVisibleLeafIndex(column); + final int newColumnIndex = columnIndex + offset; + return getTableView().getVisibleLeafColumn(newColumnIndex); + } + + private GridViewSkin getSpreadsheetViewSkin() { + return (GridViewSkin) getCellsViewSkin(); + } + + /** + * For a position, return the Visible Cell associated with It can be the top + * of the span cell if it's visible, or it can be the first row visible if + * we have scrolled + * + * @param row + * @param column + * @param col + * @return + */ + private TablePosition<ObservableList<SpreadsheetCell>, ?> getVisibleCell(int row, + TableColumn<ObservableList<SpreadsheetCell>, ?> column, int col) { + final SpreadsheetView.SpanType spanType = spreadsheetView.getSpanType(row, col); + switch (spanType) { + case NORMAL_CELL: + case ROW_VISIBLE: + return new TablePosition<>(cellsView, row, column); + case BOTH_INVISIBLE: + case COLUMN_SPAN_INVISIBLE: + case ROW_SPAN_INVISIBLE: + default: + final SpreadsheetCell cellSpan = cellsView.getItems().get(row).get(col); + if (getCellsViewSkin() == null || (getCellsViewSkin().getCellsSize() != 0 && getNonFixedRow(0).getIndex() <= cellSpan.getRow())) { + return new TablePosition<>(cellsView, cellSpan.getRow(), cellsView.getColumns().get( + cellSpan.getColumn())); + } else { // If it's not, then it's the firstkey + return new TablePosition<>(cellsView, getNonFixedRow(0).getIndex(), cellsView.getColumns().get( + cellSpan.getColumn())); + } + } + } + + /** + * @return the inner table view skin + */ + final GridViewSkin getCellsViewSkin() { + return (GridViewSkin) (cellsView.getSkin()); + } + + /** + * Return the {@link GridRow} at the specified index + * + * @param index + * @return + */ + private GridRow getNonFixedRow(int index) { + return getCellsViewSkin().getRow(index); + } + + /** + * Return the TableColumn right after the current TablePosition (including + * the ColumSpan to be on a visible Cell) + * + * @param t the current TablePosition + * @return + */ + private int getTableColumnSpanInt(final TablePosition<?, ?> t) { + return t.getColumn() + cellsView.getItems().get(t.getRow()).get(t.getColumn()).getColumnSpan(); + } + +} diff --git a/src/impl/org/controlsfx/spreadsheet/VerticalHeader.java b/src/impl/org/controlsfx/spreadsheet/VerticalHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..0cefc9ebbdfb4d0c4b3b18597021c4b582309584 --- /dev/null +++ b/src/impl/org/controlsfx/spreadsheet/VerticalHeader.java @@ -0,0 +1,675 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.spreadsheet; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.NodeOrientation; +import javafx.scene.Cursor; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; +import javafx.scene.control.ScrollBar; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView.TableViewSelectionModel; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.stage.WindowEvent; +import org.controlsfx.control.spreadsheet.Picker; +import org.controlsfx.control.spreadsheet.SpreadsheetCell; +import org.controlsfx.control.spreadsheet.SpreadsheetView; + +/** + * Display the vertical header on the left of the cells (view), the index of the + * lines displayed on screen. + */ +public class VerticalHeader extends StackPane { + + public static final int PICKER_SIZE = 16; + private static final int DRAG_RECT_HEIGHT = 5; + private static final String TABLE_ROW_KEY = "TableRow"; //$NON-NLS-1$ + private static final String PICKER_INDEX = "PickerIndex"; //$NON-NLS-1$ + private static final String TABLE_LABEL_KEY = "Label"; //$NON-NLS-1$ + private static final Image pinImage = new Image(SpreadsheetView.class.getResource("pinSpreadsheetView.png").toExternalForm()); //$NON-NLS-1$ + + /** + * ************************************************************************* + * * Private Fields * * + * ************************************************************************ + */ + private final SpreadsheetHandle handle; + private final SpreadsheetView spreadsheetView; + private double horizontalHeaderHeight; + /** + * This represents the VerticalHeader width. It's the total amount of space + * used by the VerticalHeader. It's composed of the sum of the + * SpreadsheetView {@link SpreadsheetView#getRowHeaderWidth() } and the size + * of the pickers (which is fixed right now). + * + */ + private final DoubleProperty innerVerticalHeaderWidth = new SimpleDoubleProperty(); + private Rectangle clip; // Ensure that children do not go out of bounds + private ContextMenu blankContextMenu; + + // used for column resizing + private double lastY = 0.0F; + private static double dragAnchorY = 0.0; + + // drag rectangle overlays + private final List<Rectangle> dragRects = new ArrayList<>(); + + private final List<Label> labelList = new ArrayList<>(); + private GridViewSkin skin; + private boolean resizing = false; + + private final Stack<Label> pickerPile; + private final Stack<Label> pickerUsed; + + /** + * This BitSet keeps track of the selected rows (when clicked on their + * header) in order to allow multi-resize. + */ + private final BitSet selectedRows = new BitSet(); + + /** + * **************************************************************** + * CONSTRUCTOR + * + * @param handle + * *************************************************************** + */ + public VerticalHeader(final SpreadsheetHandle handle) { + this.handle = handle; + this.spreadsheetView = handle.getView(); + pickerPile = new Stack<>(); + pickerUsed = new Stack<>(); + } + + /** + * ************************************************************************* + * * Private/Protected Methods * + * *********************************************************************** + */ + /** + * Init + * + * @param skin + * @param horizontalHeader + */ + void init(final GridViewSkin skin, HorizontalHeader horizontalHeader) { + this.skin = skin; + // Adjust position upon HorizontalHeader height + horizontalHeader.heightProperty().addListener(new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> arg0, Number oldHeight, Number newHeight) { + horizontalHeaderHeight = newHeight.doubleValue(); + requestLayout(); + } + }); + + // When the Grid is changing, we need to update our information. + handle.getView().gridProperty().addListener(layout); + + // Clip property to stay within bounds + clip = new Rectangle(getVerticalHeaderWidth(), snapSize(skin.getSkinnable().getHeight())); + clip.relocate(snappedTopInset(), snappedLeftInset()); + clip.setSmooth(false); + clip.heightProperty().bind(skin.getSkinnable().heightProperty()); + clip.widthProperty().bind(innerVerticalHeaderWidth); + VerticalHeader.this.setClip(clip); + + // We desactivate and activate the verticalHeader upon request + spreadsheetView.showRowHeaderProperty().addListener(layout); + + // When the Column header is showing or not, we need to update the + // position of the verticalHeader + spreadsheetView.showColumnHeaderProperty().addListener(layout); + spreadsheetView.getFixedRows().addListener(layout); + spreadsheetView.fixingRowsAllowedProperty().addListener(layout); + spreadsheetView.rowHeaderWidthProperty().addListener(layout); + + // In case we resize the view in any manners + spreadsheetView.heightProperty().addListener(layout); + + //When rowPickers is changing + spreadsheetView.getRowPickers().addListener(layout); + + // For layout properly the verticalHeader when there are some selected + // items + skin.getSelectedRows().addListener(layout); + + blankContextMenu = new ContextMenu(); + } + + public double getVerticalHeaderWidth() { + return innerVerticalHeaderWidth.get(); + } + + public ReadOnlyDoubleProperty verticalHeaderWidthProperty(){ + return innerVerticalHeaderWidth; + } + + public double computeHeaderWidth() { + double width = 0; + if (!spreadsheetView.getRowPickers().isEmpty()) { + width += PICKER_SIZE; + } + if (spreadsheetView.isShowRowHeader()) { + width += spreadsheetView.getRowHeaderWidth(); + } + return width; + } + + void clearSelectedRows(){ + selectedRows.clear(); + } + + @Override + protected void layoutChildren() { + if (resizing) { + return; + } + if ((spreadsheetView.isShowRowHeader() || !spreadsheetView.getRowPickers().isEmpty()) && skin.getCellsSize() > 0) { + + double x = snappedLeftInset(); + /** + * Pickers + */ + pickerPile.addAll(pickerUsed.subList(0, pickerUsed.size())); + pickerUsed.clear(); + if (!spreadsheetView.getRowPickers().isEmpty()) { + innerVerticalHeaderWidth.setValue(PICKER_SIZE); + x += PICKER_SIZE; + } else { + innerVerticalHeaderWidth.setValue(0); + } + if (spreadsheetView.isShowRowHeader()) { + innerVerticalHeaderWidth.setValue(getVerticalHeaderWidth() + spreadsheetView.getRowHeaderWidth()); + } + + getChildren().clear(); + + final int cellSize = skin.getCellsSize(); + + int rowCount = 0; + Label label; + + rowCount = addVisibleRows(rowCount, x, cellSize); + +// if (spreadsheetView.isShowRowHeader()) { + rowCount = addFixedRows(rowCount, x, cellSize); +// } + // First one blank and on top (z-order) of the others + if (spreadsheetView.showColumnHeaderProperty().get()) { + label = getLabel(rowCount++, null); + label.setOnMousePressed((MouseEvent event) -> { + spreadsheetView.getSelectionModel().selectAll(); + }); + label.setText(""); //$NON-NLS-1$ + label.resize(spreadsheetView.getRowHeaderWidth(), horizontalHeaderHeight); + label.layoutYProperty().unbind(); + label.setLayoutY(0); + label.setLayoutX(x); + label.getStyleClass().clear(); + label.setContextMenu(blankContextMenu); + getChildren().add(label); + } + + ScrollBar hbar = handle.getCellsViewSkin().getHBar(); + //FIXME handle height. + if (hbar.isVisible()) { + // Last one blank and on top (z-order) of the others + label = getLabel(rowCount++, null); + label.getProperties().put(TABLE_ROW_KEY, null); + label.setText(""); //$NON-NLS-1$ + label.resize(getVerticalHeaderWidth(), hbar.getHeight()); + label.layoutYProperty().unbind(); + label.relocate(snappedLeftInset(), getHeight() - hbar.getHeight()); + label.getStyleClass().clear(); + label.setContextMenu(blankContextMenu); + getChildren().add(label); + } + } else { + getChildren().clear(); + } + } + + private int addFixedRows(int rowCount, double x, int cellSize) { + double spaceUsedByFixedRows = 0; + int rowIndex; + Label label; + final Set<Integer> currentlyFixedRow = handle.getCellsViewSkin().getCurrentlyFixedRow(); + // Then we iterate over the FixedRows if any + if (!spreadsheetView.getFixedRows().isEmpty() && cellSize != 0) { + for (int j = 0; j < spreadsheetView.getFixedRows().size(); ++j) { + + rowIndex = spreadsheetView.getFixedRows().get(j); + if (!currentlyFixedRow.contains(rowIndex)) { + break; + } + double rowHeight = skin.getRowHeight(rowIndex); + double y = spreadsheetView.showColumnHeaderProperty().get() ? snappedTopInset() + horizontalHeaderHeight + spaceUsedByFixedRows + : snappedTopInset() + spaceUsedByFixedRows; + + if (spreadsheetView.getRowPickers().containsKey(rowIndex)) { + Label picker = getPicker(spreadsheetView.getRowPickers().get(rowIndex)); + picker.resize(PICKER_SIZE, rowHeight); + picker.layoutYProperty().unbind(); + picker.setLayoutY(y); + getChildren().add(picker); + } + if (spreadsheetView.isShowRowHeader()) { + label = getLabel(rowCount++, rowIndex); + GridRow row = skin.getRowIndexed(rowIndex); + label.getProperties().put(TABLE_ROW_KEY, row); + label.setText(getRowHeader(rowIndex)); + label.resize(spreadsheetView.getRowHeaderWidth(), rowHeight); + label.setContextMenu(getRowContextMenu(rowIndex)); + if(row != null){ + label.layoutYProperty().bind(row.layoutYProperty().add(horizontalHeaderHeight).add(row.verticalShift)); + } + label.setLayoutX(x); + final ObservableList<String> css = label.getStyleClass(); + if (skin.getSelectedRows().contains(rowIndex)) { + css.addAll("selected"); //$NON-NLS-1$ + } else { + css.removeAll("selected"); //$NON-NLS-1$ + } + css.addAll("fixed"); //$NON-NLS-1$ + getChildren().add(label); + // position drag overlay to intercept row resize requests if authorized by the grid. + if (spreadsheetView.getGrid().isRowResizable(rowIndex)) { + Rectangle dragRect = getDragRect(rowCount++); + dragRect.getProperties().put(TABLE_ROW_KEY, row); + dragRect.getProperties().put(TABLE_LABEL_KEY, label); + dragRect.setWidth(label.getWidth()); + dragRect.relocate(snappedLeftInset() + x, y + rowHeight - DRAG_RECT_HEIGHT); + getChildren().add(dragRect); + } + } + spaceUsedByFixedRows += skin.getRowHeight(rowIndex); + + + } + } + return rowCount; + } + + private int addVisibleRows(int rowCount, double x, int cellSize) { + int rowIndex; + // We add horizontalHeaderHeight because we need to + // take the other header into account. + double y = snappedTopInset(); + + if (spreadsheetView.showColumnHeaderProperty().get()) { + y += horizontalHeaderHeight; + } + + // The Labels must be aligned with the rows + if (cellSize != 0) { + y += skin.getRow(0).getLocalToParentTransform().getTy(); + } + + Label label; + // We don't want to add Label if there are no rows associated with. + final int modelRowCount = spreadsheetView.getGrid().getRowCount(); + + int i = 0; + + GridRow row = skin.getRow(i); + + double fixedRowHeight = skin.getFixedRowHeight(); + double rowHeaderWidth = spreadsheetView.getRowHeaderWidth(); + double height; + + // We iterate over the visibleRows + while (cellSize != 0 && row != null && row.getIndex() < modelRowCount) { + rowIndex = row.getIndex(); + height = row.getHeight(); + /** + * Picker + */ + if (row.getLayoutY() >= fixedRowHeight && spreadsheetView.getRowPickers().containsKey(rowIndex)) { + Label picker = getPicker(spreadsheetView.getRowPickers().get(rowIndex)); + picker.resize(PICKER_SIZE, height); + picker.layoutYProperty().bind(row.layoutYProperty().add(horizontalHeaderHeight)); + getChildren().add(picker); + } + + if (spreadsheetView.isShowRowHeader()) { + label = getLabel(rowCount++, rowIndex); + label.getProperties().put(TABLE_ROW_KEY, row); + label.setText(getRowHeader(rowIndex)); + label.resize(rowHeaderWidth, height); + label.setLayoutX(x); + label.layoutYProperty().bind(row.layoutYProperty().add(horizontalHeaderHeight)); + label.setContextMenu(getRowContextMenu(rowIndex)); + + getChildren().add(label); + // We want to highlight selected rows + final ObservableList<String> css = label.getStyleClass(); + if (skin.getSelectedRows().contains(rowIndex)) { + css.addAll("selected"); //$NON-NLS-1$ + } else { + css.removeAll("selected"); //$NON-NLS-1$ + } + if (spreadsheetView.getFixedRows().contains(rowIndex)) { + css.addAll("fixed"); //$NON-NLS-1$ + } else { + css.removeAll("fixed"); //$NON-NLS-1$ + } + + y += height; + + // position drag overlay to intercept row resize requests if authorized by the grid. + if (spreadsheetView.getGrid().isRowResizable(rowIndex)) { + Rectangle dragRect = getDragRect(rowCount++); + dragRect.getProperties().put(TABLE_ROW_KEY, row); + dragRect.getProperties().put(TABLE_LABEL_KEY, label); + dragRect.setWidth(label.getWidth()); + dragRect.relocate(snappedLeftInset() + x, y - DRAG_RECT_HEIGHT); + getChildren().add(dragRect); + } + } + row = skin.getRow(++i); + } + return rowCount; + } + + private final EventHandler<MouseEvent> rectMousePressed = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent me) { + + if (me.getClickCount() == 2 && me.isPrimaryButtonDown()) { + Rectangle rect = (Rectangle) me.getSource(); + GridRow row = (GridRow) rect.getProperties().get(TABLE_ROW_KEY); + skin.resizeRowToFitContent(row.getIndex()); + requestLayout(); + } else { + // rather than refer to the rect variable, we just grab + // it from the source to prevent a small memory leak. + dragAnchorY = me.getSceneY(); + resizing = true; + } + me.consume(); + } + }; + + private final EventHandler<MouseEvent> rectMouseDragged = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent me) { + Rectangle rect = (Rectangle) me.getSource(); + GridRow row = (GridRow) rect.getProperties().get(TABLE_ROW_KEY); + Label label = (Label) rect.getProperties().get(TABLE_LABEL_KEY); + if (row != null) { + rowResizing(row, label, me); + } + me.consume(); + } + }; + + private void rowResizing(GridRow gridRow, Label label, MouseEvent me) { + double draggedY = me.getSceneY() - dragAnchorY; + if (gridRow.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) { + draggedY = -draggedY; + } + + double delta = draggedY - lastY; + + Double newHeight = gridRow.getHeight() + delta; + if (newHeight < 0) { + return; + } + handle.getCellsViewSkin().rowHeightMap.put(gridRow.getIndex(), newHeight); + Event.fireEvent(spreadsheetView, new SpreadsheetView.RowHeightEvent(gridRow.getIndex(), newHeight)); + label.resize(spreadsheetView.getRowHeaderWidth(), newHeight); + gridRow.setPrefHeight(newHeight); + gridRow.requestLayout(); + + lastY = draggedY; + } + + private final EventHandler<MouseEvent> rectMouseReleased = new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent me) { + lastY = 0.0F; + resizing = false; + requestLayout(); + me.consume(); + //We resize the other selected rows if the resized one is selected. + Rectangle rect = (Rectangle) me.getSource(); + GridRow row = (GridRow) rect.getProperties().get(TABLE_ROW_KEY); + if (selectedRows.get(row.getIndex())) { + double height = row.getHeight(); + for (int i = selectedRows.nextSetBit(0); i >= 0; i = selectedRows.nextSetBit(i + 1)) { + skin.rowHeightMap.put(i, height); + Event.fireEvent(spreadsheetView, new SpreadsheetView.RowHeightEvent(i, height)); + } + } + } + }; + + /** + * Create a new label and put it in the pile or just grab one from the pile. + * + * @param rowNumber + * @return + */ + private Label getLabel(int rowNumber, Integer row) { + Label label; + if (labelList.isEmpty() || labelList.size() <= rowNumber) { + label = new Label(); + labelList.add(label); + } else { + label = labelList.get(rowNumber); + } + // We want to select the whole row when clicking on a header. + label.setOnMousePressed(row == null ? null : (MouseEvent event) -> { + if (event.isPrimaryButtonDown()) { + if (event.getClickCount() == 2) { + skin.resizeRowToFitContent(row); + requestLayout(); + } else { + headerClicked(row, event); + } + } + }); + return label; + } + + /** + * If a header is clicked, we must select the whole row. If Control key of + * Shift key is pressed, we must not deselect the previous selection but + * just act like the {@link GridViewBehavior} would. + * + * @param row + * @param event + */ + private void headerClicked(int row, MouseEvent event) { + TableViewSelectionModel<ObservableList<SpreadsheetCell>> sm = handle.getGridView().getSelectionModel(); + int focusedRow = sm.getFocusedIndex(); + int rowCount = handle.getView().getGrid().getRowCount(); + ObservableList<TableColumn<ObservableList<SpreadsheetCell>, ?>> columns = sm.getTableView().getColumns(); + TableColumn<ObservableList<SpreadsheetCell>, ?> firstColumn = columns.get(0); + TableColumn<ObservableList<SpreadsheetCell>, ?> lastColumn = columns.get(columns.size() - 1); + + if (event.isShortcutDown()) { + BitSet tempSet = (BitSet) selectedRows.clone(); + sm.selectRange(row, firstColumn, row, lastColumn); + selectedRows.or(tempSet); + selectedRows.set(row); + } else if (event.isShiftDown() && focusedRow >= 0 && focusedRow < rowCount) { + sm.clearSelection(); + sm.selectRange(focusedRow, firstColumn, row, lastColumn); + //We want to let the focus on the focused row. + sm.getTableView().getFocusModel().focus(focusedRow, firstColumn); + int min = Math.min(row, focusedRow); + int max = Math.max(row, focusedRow); + selectedRows.set(min, max + 1); + } else { + sm.clearSelection(); + sm.selectRange(row, firstColumn, row, lastColumn); + //And we want to have the focus on the first cell in order to be able to copy/paste between rows. + sm.getTableView().getFocusModel().focus(row, firstColumn); + selectedRows.set(row); + } + } + + private Label getPicker(Picker picker) { + Label pickerLabel; + if (pickerPile.isEmpty()) { + pickerLabel = new Label(); + picker.getStyleClass().addListener(layout); + pickerLabel.setOnMouseClicked(pickerMouseEvent); + } else { + pickerLabel = pickerPile.pop(); + } + pickerUsed.push(pickerLabel); + + pickerLabel.getStyleClass().setAll(picker.getStyleClass()); + pickerLabel.getProperties().put(PICKER_INDEX, picker); + return pickerLabel; + } + + private final EventHandler<MouseEvent> pickerMouseEvent = new EventHandler<MouseEvent>() { + + @Override + public void handle(MouseEvent mouseEvent) { + Label picker = (Label) mouseEvent.getSource(); + + ((Picker) picker.getProperties().get(PICKER_INDEX)).onClick(); + } + }; + + /** + * Create a new Rectangle and put it in the pile or just grab one from the + * pile. + * + * @param rowNumber + * @return + */ + private Rectangle getDragRect(int rowNumber) { + if (dragRects.isEmpty() || dragRects.size() <= rowNumber) { + final Rectangle rect = new Rectangle(); + rect.setWidth(getVerticalHeaderWidth()); + rect.setHeight(DRAG_RECT_HEIGHT); + rect.setFill(Color.TRANSPARENT); + rect.setSmooth(false); + rect.setOnMousePressed(rectMousePressed); + rect.setOnMouseDragged(rectMouseDragged); + rect.setOnMouseReleased(rectMouseReleased); + rect.setCursor(Cursor.V_RESIZE); + dragRects.add(rect); + return rect; + } else { + return dragRects.get(rowNumber); + } + } + + /** + * Return a contextMenu for fixing a row if possible. + * + * @param row + * @return + */ + private ContextMenu getRowContextMenu(final Integer row) { + if (spreadsheetView.isRowFixable(row)) { + final ContextMenu contextMenu = new ContextMenu(); + + MenuItem fixItem = new MenuItem(localize(asKey("spreadsheet.verticalheader.menu.fix"))); //$NON-NLS-1$ + contextMenu.setOnShowing(new EventHandler<WindowEvent>() { + + @Override + public void handle(WindowEvent event) { + if (spreadsheetView.getFixedRows().contains(row)) { + fixItem.setText(localize(asKey("spreadsheet.verticalheader.menu.unfix"))); //$NON-NLS-1$ + } else { + fixItem.setText(localize(asKey("spreadsheet.verticalheader.menu.fix"))); //$NON-NLS-1$ + } + } + }); + fixItem.setGraphic(new ImageView(pinImage)); + + fixItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent arg0) { + if (spreadsheetView.getFixedRows().contains(row)) { + spreadsheetView.getFixedRows().remove(row); + } else { + spreadsheetView.getFixedRows().add(row); + } + } + }); + contextMenu.getItems().add(fixItem); + + return contextMenu; + } else { + return blankContextMenu; + } + } + + /** + * Return the String header associated with this row index. + * + * @param index + * @return + */ + private String getRowHeader(int index) { + return spreadsheetView.getGrid().getRowHeaders().size() > index ? spreadsheetView + .getGrid().getRowHeaders().get(index) : String.valueOf(index + 1); + } + + /** + * ************************************************************************* + * * Listeners * * + * ************************************************************************ + */ + private final InvalidationListener layout = (Observable arg0) -> { + requestLayout(); + }; +} diff --git a/src/impl/org/controlsfx/table/ColumnFilter.java b/src/impl/org/controlsfx/table/ColumnFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..50d79c6af886917c9c7e5d98945e54df7e648929 --- /dev/null +++ b/src/impl/org/controlsfx/table/ColumnFilter.java @@ -0,0 +1,308 @@ +/** + * Copyright (c) 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.table; + +import javafx.beans.Observable; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.WeakListChangeListener; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.TableColumn; +import org.controlsfx.control.table.TableFilter; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiPredicate; + +public final class ColumnFilter<T,R> { + private final TableFilter<T> tableFilter; + private final TableColumn<T,R> tableColumn; + + private final ObservableList<FilterValue<T,R>> filterValues; + + private final DupeCounter<R> filterValuesDupeCounter = new DupeCounter<>(false); + private final DupeCounter<R> visibleValuesDupeCounter = new DupeCounter<>(false); + private final HashSet<R> unselectedValues = new HashSet<>(); + private final HashMap<CellIdentity<T>,ChangeListener<R>> trackedCells = new HashMap<>(); + + private boolean lastFilter = false; + private boolean isDirty = false; + private BiPredicate<String,String> searchStrategy = (inputString, subjectString) -> subjectString.contains(inputString); + private volatile FilterPanel filterPanel; + + private boolean initialized = false; + + private final ListChangeListener<T> backingListListener = lc -> { + while (lc.next()) { + if (lc.wasAdded()) { + lc.getAddedSubList().stream() + .forEach(t -> addBackingItem(t, getTableColumn().getCellObservableValue(t))); + } + if (lc.wasRemoved()) { + lc.getRemoved().stream() + .forEach(t -> removeBackingItem(t, getTableColumn().getCellObservableValue(t))); + } + } + }; + + private final ListChangeListener<T> itemsListener = lc -> { + while (lc.next()) { + if (lc.wasAdded()) { + lc.getAddedSubList().stream() + .map(getTableColumn()::getCellObservableValue) + .forEach(this::addVisibleItem); + } + if (lc.wasRemoved()) { + lc.getRemoved().stream() + .map(getTableColumn()::getCellObservableValue) + .forEach(this::removeVisibleItem); + } + } + }; + + private final ChangeListener<R> changeListener = (observable, oldValue, newValue) -> { + if (filterValuesDupeCounter.add(newValue) == 1) { + getFilterValues().add(new FilterValue<>(newValue,this)); + } + removeValue(oldValue); + }; + + private final ListChangeListener<FilterValue<T, R>> filterValueListChangeListener = lc -> { + while (lc.next()) { + if (lc.wasRemoved()) { + lc.getRemoved().stream() + .filter(v -> !v.selectedProperty().get()) + .forEach(unselectedValues::remove); + } + if (lc.wasUpdated()) { + int from = lc.getFrom(); + int to = lc.getTo(); + lc.getList().subList(from, to).forEach(v -> { + isDirty = true; + + boolean value = v.selectedProperty().getValue(); + if (!value) { + unselectedValues.add(v.getValue()); + } else { + unselectedValues.remove(v.getValue()); + } + }); + } + } + }; + + public ColumnFilter(TableFilter<T> tableFilter, TableColumn<T,R> tableColumn) { + this.tableFilter = tableFilter; + this.tableColumn = tableColumn; + + this.filterValues = FXCollections.observableArrayList(cb -> new Observable[] { cb.selectedProperty()}); + this.attachContextMenu(); + } + void setFilterPanel(FilterPanel filterPanel) { + this.filterPanel = filterPanel; + } + FilterPanel getFilterPanel() { + return filterPanel; + } + public void initialize() { + if (!initialized) { + initializeListeners(); + initializeValues(); + initialized = true; + } + } + public boolean isInitialized() { + return initialized; + } + + public void selectValue(Object value) { + filterPanel.selectValue(value); + } + public void unselectValue(Object value) { + filterPanel.unSelectValue(value); + } + public void selectAllValues() { + filterPanel.selectAllValues(); + } + public void unSelectAllValues() { + filterPanel.unSelectAllValues(); + } + public boolean wasLastFiltered() { + return lastFilter; + } + public boolean hasUnselections() { + return unselectedValues.size() != 0; + } + public void setSearchStrategy(BiPredicate<String,String> searchStrategy) { + this.searchStrategy = searchStrategy; + } + public BiPredicate<String,String> getSearchStrategy() { + return searchStrategy; + } + public boolean isFiltered() { + return isDirty || unselectedValues.size() > 0; + } + public boolean valueIsVisible(R value) { + return visibleValuesDupeCounter.get(value) > 0; + } + public void applyFilter() { + tableFilter.executeFilter(); + lastFilter = true; + tableFilter.getColumnFilters().stream().filter(c -> !c.equals(this)).forEach(c -> c.lastFilter = false); + tableFilter.getColumnFilters().stream().flatMap(c -> c.filterValues.stream()).forEach(FilterValue::refreshScope); + isDirty = false; + } + + public void resetAllFilters() { + tableFilter.getColumnFilters().stream().flatMap(c -> c.filterValues.stream()).forEach(fv -> fv.selectedProperty().set(true)); + tableFilter.resetFilter(); + tableFilter.getColumnFilters().stream().forEach(c -> c.lastFilter = false); + tableFilter.getColumnFilters().stream().flatMap(c -> c.filterValues.stream()).forEach(FilterValue::refreshScope); + isDirty = false; + } + + public ObservableList<FilterValue<T,R>> getFilterValues() { + return filterValues; + } + + public TableColumn<T,R> getTableColumn() { + return tableColumn; + } + public TableFilter<T> getTableFilter() { + return tableFilter; + } + public boolean evaluate(T item) { + ObservableValue<R> value = tableColumn.getCellObservableValue(item); + + return unselectedValues.size() == 0 + || !unselectedValues.contains(value.getValue()); + } + + private void initializeValues() { + tableFilter.getBackingList().stream() + .forEach(t -> addBackingItem(t, tableColumn.getCellObservableValue(t))); + tableFilter.getTableView().getItems().stream() + .map(tableColumn::getCellObservableValue).forEach(this::addVisibleItem); + + } + + private void addBackingItem(T item, ObservableValue<R> cellValue) { + if (cellValue == null) { + return; + } + if (filterValuesDupeCounter.add(cellValue.getValue()) == 1) { + filterValues.add(new FilterValue<>(cellValue.getValue(),this)); + } + + //listen to cell value and track it + CellIdentity<T> trackedCellValue = new CellIdentity<>(item); + + ChangeListener<R> cellListener = new WeakChangeListener(changeListener); + cellValue.addListener(cellListener); + trackedCells.put(trackedCellValue,cellListener); + } + private void removeBackingItem(T item, ObservableValue<R> cellValue) { + if (cellValue == null) { + return; + } + removeValue(cellValue.getValue()); + + //remove listener from cell + ChangeListener<R> listener = trackedCells.get(new CellIdentity<>(item)); + cellValue.removeListener(listener); + trackedCells.remove(new CellIdentity<>(item)); + } + private void removeValue(R value) { + boolean removedLastDuplicate = filterValuesDupeCounter.remove(value) == 0; + if (removedLastDuplicate) { + // Remove the FilterValue associated with the value + FilterValue<T,R> existingFilterValue = getFilterValues().stream() + .filter(fv -> Objects.equals(fv.getValue(), value)).findAny().get(); + getFilterValues().remove(existingFilterValue); + } + } + private void addVisibleItem(ObservableValue<R> cellValue) { + if (cellValue != null) { + visibleValuesDupeCounter.add(cellValue.getValue()); + } + } + private void removeVisibleItem(ObservableValue<R> cellValue) { + if (cellValue != null) { + visibleValuesDupeCounter.remove(cellValue.getValue()); + } + } + private void initializeListeners() { + //listen to backing list and update distinct values accordingly + tableFilter.getBackingList().addListener(new WeakListChangeListener<T>(backingListListener)); + + //listen to visible items and update visible values accordingly + tableFilter.getTableView().getItems().addListener(new WeakListChangeListener<T>(itemsListener)); + + //listen to selections on filterValues + filterValues.addListener(new WeakListChangeListener<>(filterValueListChangeListener)); + } + + /**Leverages tableColumn's context menu to attach filter panel */ + private void attachContextMenu() { + + ContextMenu contextMenu = new ContextMenu(); + + CustomMenuItem item = FilterPanel.getInMenuItem(this, contextMenu); + + contextMenu.getStyleClass().add("column-filter"); + contextMenu.getItems().add(item); + + tableColumn.setContextMenu(contextMenu); + + contextMenu.setOnShowing(ae -> initialize()); + } + + private static final class CellIdentity<T> { + private final T item; + + CellIdentity(T item) { + this.item = item; + } + + @Override + public boolean equals(Object other) { + return this.item == ((CellIdentity<?>)other).item; + } + + @Override + public int hashCode() { + return System.identityHashCode(item); + } + } +} diff --git a/src/impl/org/controlsfx/table/DupeCounter.java b/src/impl/org/controlsfx/table/DupeCounter.java new file mode 100644 index 0000000000000000000000000000000000000000..851a6ce4e8ff14dd43e3fe5d587bce7c512602d5 --- /dev/null +++ b/src/impl/org/controlsfx/table/DupeCounter.java @@ -0,0 +1,52 @@ +package impl.org.controlsfx.table; + +import java.util.HashMap; +import java.util.Optional; + +final class DupeCounter<T> { + + private final HashMap<T,Integer> counts = new HashMap<>(); + private final boolean enforceFloor; + + public DupeCounter(boolean enforceFloor) { + this.enforceFloor = enforceFloor; + } + public int add(T value) { + Integer prev = counts.get(value); + int newVal; + if (prev == null) { + newVal = 1; + counts.put(value, newVal); + } else { + newVal = prev + 1; + counts.put(value, newVal); + } + return newVal; + } + public int get(T value) { + return Optional.ofNullable(counts.get(value)).orElse(0); + } + public int remove(T value) { + Integer prev = counts.get(value); + if (prev != null && prev > 0) { + int newVal = prev - 1; + if (newVal == 0) { + counts.remove(value); + } else { + counts.put(value, newVal); + } + return newVal; + } + else if (enforceFloor) { + throw new IllegalStateException(); + } + else { + return 0; + } + } + + @Override + public String toString() { + return counts.toString(); + } +} diff --git a/src/impl/org/controlsfx/table/FilterPanel.java b/src/impl/org/controlsfx/table/FilterPanel.java new file mode 100644 index 0000000000000000000000000000000000000000..3f417bac3ebaa97becea8826913e7f2e5bc05296 --- /dev/null +++ b/src/impl/org/controlsfx/table/FilterPanel.java @@ -0,0 +1,275 @@ +/** + * Copyright (c) 2015, 2016, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.table; + +import com.sun.javafx.scene.control.skin.NestedTableColumnHeader; +import com.sun.javafx.scene.control.skin.TableColumnHeader; +import com.sun.javafx.scene.control.skin.TableViewSkin; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.geometry.Side; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Supplier; + + +public final class FilterPanel<T,R> extends VBox { + + private final ColumnFilter<T,R> columnFilter; + + private final FilteredList<FilterValue> filterList; + private static final String promptText = "Search..."; + private final TextField searchBox = new TextField(); + private boolean searchMode = false; + private boolean bumpedWidth = false; + + private final ListView<FilterValue> checkListView; + + // This collection will reference column header listeners. References must be kept locally because weak listeners are registered + private final Collection<InvalidationListener> columnHeadersChangeListeners = new ArrayList(); + + private static final Image filterIcon = new Image("/impl/org/controlsfx/table/filter.png"); + + private static final Supplier<ImageView> filterImageView = () -> { + ImageView imageView = new ImageView(filterIcon); + imageView.setFitHeight(15); + imageView.setPreserveRatio(true); + return imageView; + }; + + private final ChangeListener<Skin<?>> skinListener = (w, o, n) -> { + // Clear references to listeners, this will (eventually) cause the WeakListeners to expire + columnHeadersChangeListeners.clear(); + + if (n instanceof TableViewSkin) { + TableViewSkin<?> skin = (TableViewSkin<?>) n; + checkChangeContextMenu(skin, getColumnFilter().getTableColumn(), this); + } + }; + + void selectAllValues() { + checkListView.getItems().stream() + .forEach(item -> item.selectedProperty().set(true)); + } + void unSelectAllValues() { + checkListView.getItems().stream() + .forEach(item -> item.selectedProperty().set(false)); + } + void selectValue(Object value) { + checkListView.getItems().stream().filter(item -> item.getValue().equals(value)) + .forEach(item -> item.selectedProperty().set(true)); + } + void unSelectValue(Object value) { + checkListView.getItems().stream().filter(item -> item.getValue() == value) + .forEach(item -> item.selectedProperty().set(false)); + } + + FilterPanel(ColumnFilter<T,R> columnFilter, ContextMenu contextMenu) { + columnFilter.setFilterPanel(this); + this.columnFilter = columnFilter; + getStyleClass().add("filter-panel"); + + //initialize search box + setPadding(new Insets(3)); + + searchBox.setPromptText(promptText); + getChildren().add(searchBox); + + //initialize checklist view + + filterList = new FilteredList<>(new SortedList<>(columnFilter.getFilterValues()), t -> true); + checkListView = new ListView<>(); + checkListView.setItems(new SortedList<>(filterList, FilterValue::compareTo)); + + getChildren().add(checkListView); + + //initialize apply button + HBox buttonBox = new HBox(); + + Button applyBttn = new Button("APPLY"); + HBox.setHgrow(applyBttn, Priority.ALWAYS); + + applyBttn.setOnAction(e -> { + if (searchMode) { + filterList.forEach(v -> v.selectedProperty().setValue(true)); + + columnFilter.getFilterValues().stream() + .filter(v -> !filterList.stream().filter(fl -> fl.equals(v)).findAny().isPresent()) + .forEach(v -> v.selectedProperty().setValue(false)); + + resetSearchFilter(); + } + if (columnFilter.getTableFilter().isDirty()) { + columnFilter.applyFilter(); + columnFilter.getTableFilter().getColumnFilters().stream().map(ColumnFilter::getFilterPanel) + .forEach(fp -> { + if (!fp.columnFilter.hasUnselections()) { + fp.columnFilter.getTableColumn().setGraphic(null); + } else { + fp.columnFilter.getTableColumn().setGraphic(filterImageView.get()); + if (!bumpedWidth) { + fp.columnFilter.getTableColumn().setPrefWidth(columnFilter.getTableColumn().getWidth() + 20); + bumpedWidth = true; + } + } + }); + } + contextMenu.hide(); + }); + + buttonBox.getChildren().add(applyBttn); + + //initialize unselect all button + Button unselectAllButton = new Button("NONE"); + HBox.setHgrow(unselectAllButton, Priority.ALWAYS); + + unselectAllButton.setOnAction(e -> columnFilter.getFilterValues().forEach(v -> v.selectedProperty().set(false))); + buttonBox.getChildren().add(unselectAllButton); + + //initialize reset buttons + Button selectAllButton = new Button("ALL"); + HBox.setHgrow(selectAllButton, Priority.ALWAYS); + + selectAllButton.setOnAction(e -> { + columnFilter.getFilterValues().forEach(v -> v.selectedProperty().set(true)); + }); + + buttonBox.getChildren().add(selectAllButton); + + Button clearAllButton = new Button("RESET ALL"); + HBox.setHgrow(clearAllButton, Priority.ALWAYS); + + clearAllButton.setOnAction(e -> { + columnFilter.resetAllFilters(); + columnFilter.getTableFilter().getColumnFilters().stream().forEach(cf -> cf.getTableColumn().setGraphic(null)); + contextMenu.hide(); + }); + buttonBox.getChildren().add(clearAllButton); + + buttonBox.setAlignment(Pos.BASELINE_CENTER); + + + getChildren().add(buttonBox); + } + + public void resetSearchFilter() { + this.filterList.setPredicate(t -> true); + searchBox.clear(); + } + public static <T,R> CustomMenuItem getInMenuItem(ColumnFilter<T,R> columnFilter, ContextMenu contextMenu) { + + FilterPanel<T,R> filterPanel = new FilterPanel<>(columnFilter, contextMenu); + + CustomMenuItem menuItem = new CustomMenuItem(); + + filterPanel.initializeListeners(); + + menuItem.contentProperty().set(filterPanel); + + columnFilter.getTableFilter().getTableView().skinProperty().addListener(new WeakChangeListener<>(filterPanel.skinListener)); + + menuItem.setHideOnClick(false); + return menuItem; + } + private void initializeListeners() { + searchBox.textProperty().addListener(l -> { + searchMode = !searchBox.getText().isEmpty(); + filterList.setPredicate(val -> searchBox.getText().isEmpty() || + columnFilter.getSearchStrategy().test(searchBox.getText(), Optional.ofNullable(val.getValue()).map(Object::toString).orElse(""))); + }); + } + + /* Methods below helps will anchor the context menu under the column */ + private static void checkChangeContextMenu(TableViewSkin<?> skin, TableColumn<?, ?> column, FilterPanel filterPanel) { + NestedTableColumnHeader header = skin.getTableHeaderRow().getRootHeader(); + InvalidationListener listener = filterPanel.getOrCreateChangeListener(header, column); + header.getColumnHeaders().addListener(new WeakInvalidationListener(listener)); + changeContextMenu(header, column); + } + + private InvalidationListener getOrCreateChangeListener(NestedTableColumnHeader header, TableColumn<?, ?> column) { + InvalidationListener listener = (Observable obs) -> changeContextMenu(header, column); + + // Keep a reference locally because this listener will be used with a WeakInvalidationListener + columnHeadersChangeListeners.add(listener); + + return listener; + } + + private static void changeContextMenu(NestedTableColumnHeader header, TableColumn<?, ?> column) { + TableColumnHeader headerSkin = scan(column, header); + if (headerSkin != null) { + headerSkin.setOnContextMenuRequested(ev -> { + ContextMenu cMenu = column.getContextMenu(); + if (cMenu != null) { + cMenu.show(headerSkin, Side.BOTTOM, 5, 5); + } + ev.consume(); + }); + } + } + + private static TableColumnHeader scan(TableColumn<?, ?> search, + TableColumnHeader header) { + // firstly test that the parent isn't what we are looking for + if (search.equals(header.getTableColumn())) { + return header; + } + + if (header instanceof NestedTableColumnHeader) { + NestedTableColumnHeader parent = (NestedTableColumnHeader) header; + for (int i = 0; i < parent.getColumnHeaders().size(); i++) { + TableColumnHeader result = scan(search, parent + .getColumnHeaders().get(i)); + if (result != null) { + return result; + } + } + } + + return null; + } + + public ColumnFilter<T,R> getColumnFilter() { + return columnFilter; + } +} diff --git a/src/impl/org/controlsfx/table/FilterValue.java b/src/impl/org/controlsfx/table/FilterValue.java new file mode 100644 index 0000000000000000000000000000000000000000..fc00c0591320f62aaa92dc451e7a285dd59d890a --- /dev/null +++ b/src/impl/org/controlsfx/table/FilterValue.java @@ -0,0 +1,68 @@ +package impl.org.controlsfx.table; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; + +import java.util.Optional; + +final class FilterValue<T,R> extends HBox implements Comparable<FilterValue<T,R>> { + + private final R value; + private final BooleanProperty isSelected = new SimpleBooleanProperty(true); + private final BooleanProperty inScope = new SimpleBooleanProperty(true); + private final ColumnFilter<T,R> columnFilter; + private final InvalidationListener scopeListener; + + + FilterValue(R value, ColumnFilter<T,R> columnFilter) { + this.value = value; + this.columnFilter = columnFilter; + + final CheckBox checkBox = new CheckBox(); + final Label label = new Label(); + label.setText(Optional.ofNullable(value).map(Object::toString).orElse(null)); + scopeListener = (Observable v) -> label.textFillProperty().set(getInScopeProperty().get() ? Color.BLACK : Color.LIGHTGRAY); + inScope.addListener(new WeakInvalidationListener(scopeListener)); + checkBox.selectedProperty().bindBidirectional(selectedProperty()); + getChildren().addAll(checkBox,label); + } + + public R getValue() { + return value; + } + + public BooleanProperty selectedProperty() { + return isSelected; + } + public BooleanProperty getInScopeProperty() { + return inScope; + } + + public void refreshScope() { + inScope.setValue(columnFilter.wasLastFiltered() || columnFilter.valueIsVisible(value)); + } + + @Override + public String toString() { + return Optional.ofNullable(value).map(Object::toString).orElse(""); + } + + + @Override + public int compareTo(FilterValue<T,R> other) { + if (value != null && other.value != null) { + if (value instanceof Comparable<?> && other.value instanceof Comparable<?>) { + return ((Comparable<Object>) value).compareTo(((Comparable<Object>) other.value)); + } + } + return Optional.ofNullable(value).map(Object::toString).orElse("") + .compareTo(Optional.ofNullable(other).map(Object::toString).orElse("")); + } +} diff --git a/src/impl/org/controlsfx/table/filter.png b/src/impl/org/controlsfx/table/filter.png new file mode 100644 index 0000000000000000000000000000000000000000..1098d3ac27a2c86136b60e2fb5e1b5e49bac6dfe Binary files /dev/null and b/src/impl/org/controlsfx/table/filter.png differ diff --git a/src/impl/org/controlsfx/table/no_filter.png b/src/impl/org/controlsfx/table/no_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..8861914d3125d46120d870d15c94385224f37fae Binary files /dev/null and b/src/impl/org/controlsfx/table/no_filter.png differ diff --git a/src/impl/org/controlsfx/table/tablefilter.css b/src/impl/org/controlsfx/table/tablefilter.css new file mode 100644 index 0000000000000000000000000000000000000000..96e4c327ef8e30bab2a51ade1b1454ae981c9950 --- /dev/null +++ b/src/impl/org/controlsfx/table/tablefilter.css @@ -0,0 +1,7 @@ +.column-filter .custom-menu-item:focused { + -fx-background-color: transparent; +} + +.filter-panel { + -fx-spacing: 10px +} \ No newline at end of file diff --git a/src/impl/org/controlsfx/tools/MathTools.java b/src/impl/org/controlsfx/tools/MathTools.java new file mode 100644 index 0000000000000000000000000000000000000000..6284bf63357e6980056497c2d986ed737d31fe7a --- /dev/null +++ b/src/impl/org/controlsfx/tools/MathTools.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools; + +import java.util.Objects; + +/** + * Contains methods {@link Math} might also contain but doesn't. + */ +public class MathTools { + + /** + * Checks whether the specified value lies in the closed interval defined by the specified bounds. + * + * @param lowerBound + * the interval's lower bound; included in the interval + * @param value + * the value which will be checked + * @param upperBound + * the interval's upper bound; included in the interval + * @return {@code true} if {@code lowerBound} <= {@code value} <= {@code upperBound} <br> + * {@code false} otherwise + */ + public static boolean isInInterval(double lowerBound, double value, double upperBound) { + return lowerBound <= value && value <= upperBound; + } + + /** + * Checks whether the specified value lies in the closed interval defined by the specified bounds. If it does, it is + * returned; otherwise the bound closer to the value will be returned. + * + * @param lowerBound + * the interval's lower bound; included in the interval + * @param value + * the value which will be checked + * @param upperBound + * the interval's upper bound; included in the interval + * @return {@code value} if {@code lowerBound} <= {@code value} <= {@code upperBound} <br> + * {@code lowerBound} if {@code value} < {@code lowerBound} <br> + * {@code upperBound} if {@code upperBound} < {@code value} + */ + public static double inInterval(double lowerBound, double value, double upperBound) { + if (value < lowerBound) + return lowerBound; + if (upperBound < value) + return upperBound; + return value; + } + + /** + * Returns the smallest value in the specified array according to {@link Math#min(double, double)}. + * + * @param values + * a non-null, non-empty array of double values + * @return a value from the array which is smaller then or equal to any other value from the array + * @throws NullPointerException + * if the values array is {@code null} + * @throws IllegalArgumentException + * if the values array is empty (i.e. has {@code length} 0) + */ + public static double min(double... values) { + Objects.requireNonNull(values, "The specified value array must not be null."); //$NON-NLS-1$ + if (values.length == 0) + throw new IllegalArgumentException("The specified value array must contain at least one element."); //$NON-NLS-1$ + + double min = Double.MAX_VALUE; + for (double value : values) + min = Math.min(value, min); + return min; + } + +} diff --git a/src/impl/org/controlsfx/tools/PrefixSelectionCustomizer.java b/src/impl/org/controlsfx/tools/PrefixSelectionCustomizer.java new file mode 100644 index 0000000000000000000000000000000000000000..c03367cb169449536f2541edf25f10e0214ff51f --- /dev/null +++ b/src/impl/org/controlsfx/tools/PrefixSelectionCustomizer.java @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.controlsfx.control.PrefixSelectionChoiceBox; +import org.controlsfx.control.PrefixSelectionComboBox; + +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Control; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.util.StringConverter; + +/** + * <p>This utility class can be used to customize a {@link ChoiceBox} or + * {@link ComboBox} and enable the "prefix selection" feature. This will enable + * the user to type letters or digits on the keyboard while the {@link ChoiceBox} + * or {@link ComboBox} has the focus. The {@link ChoiceBox} or {@link ComboBox} + * will attempt to select the first item it can find with a matching prefix ignoring + * case. + * + * <p>This feature is available natively on the Windows combo box control, so many + * users have asked for it. There is a feature request to include this feature + * into JavaFX (<a href="https://javafx-jira.kenai.com/browse/RT-18064">Issue RT-18064</a>). + * The class is published as part of ContorlsFX to allow testing and feedback. + * + * <h3>Example</h3> + * + * <p>Let's look at an example to clarify this. The combo box offers the items + * ["Aaaaa", "Abbbb", "Abccc", "Abcdd", "Abcde"]. The user now types "abc" in + * quick succession (and then stops typing). The combo box will select a new entry + * on every key pressed. The first entry it will select is "Aaaaa" since it is the + * first entry that starts with an "a" (case ignored). It will then select "Abbbb", + * since this is the first entry that started with "ab" and will finally settle for + * "Abccc". + * + * <ul><table> + * <tr><th>Keys typed</th><th>Element selected</th></tr> + * <tr><td>a</td><td>Aaaaa<td></tr> + * <tr><td>aaa</td><td>Aaaaa<td></tr> + * <tr><td>ab</td><td>Abbbb<td></tr> + * <tr><td>abc</td><td>Abccc<td></tr> + * <tr><td>xyz</td><td>-<td></tr> + * </table></ul> + * + * <h3>Usage</h3> + * + * <p>A common use case is to customize a {@link ChoiceBox} or {@link ComboBox} + * that has been loaded as part of an FXML. In this case you can use the utility + * methods {@link #customize(ChoiceBox)} or {@link #customize(ComboBox)}. This + * will install a {@link EventHandler} that monitors the {@link KeyEvent} + * events. + * + * <p>If you are coding, you can also use the preconfigured classes + * {@link PrefixSelectionChoiceBox} and {@link PrefixSelectionComboBox} as a + * substitute for {@link ChoiceBox} and {@link ComboBox}. + * + * @see PrefixSelectionChoiceBox + * @see PrefixSelectionComboBox + */ +public class PrefixSelectionCustomizer { + private static final String SELECTION_PREFIX_STRING = "selectionPrefixString"; + private static final Object SELECTION_PREFIX_TASK = "selectionPrefixTask"; + + private static EventHandler<KeyEvent> handler = new EventHandler<KeyEvent>() { + private ScheduledExecutorService executorService = null; + + @Override + public void handle(KeyEvent event) { + keyPressed(event); + } + + private <T> void keyPressed(KeyEvent event) { + KeyCode code = event.getCode(); + if (code.isLetterKey() || code.isDigitKey() || code == KeyCode.SPACE) { + String letter = code.impl_getChar(); + if (event.getSource() instanceof ComboBox) { + ComboBox<T> comboBox = (ComboBox<T>) event.getSource(); + T item = getEntryWithKey(letter, comboBox.getConverter(), comboBox.getItems(), comboBox); + if (item != null) { + comboBox.setValue(item); + } + } else if (event.getSource() instanceof ChoiceBox) { + ChoiceBox<T> choiceBox = (ChoiceBox<T>) event.getSource(); + T item = getEntryWithKey(letter, choiceBox.getConverter(), choiceBox.getItems(), choiceBox); + if (item != null) { + choiceBox.setValue(item); + } + } + } + } + + private <T> T getEntryWithKey(String letter, StringConverter<T> converter, ObservableList<T> items, Control control) { + T result = null; + + // The converter is null by default for the ChoiceBox. The ComboBox has a default converter + if (converter == null) { + converter = new StringConverter<T>() { + @Override + public String toString(T t) { + return t == null ? null : t.toString(); + } + + @Override + public T fromString(String string) { + return null; + } + }; + } + + String selectionPrefixString = (String) control.getProperties().get(SELECTION_PREFIX_STRING); + if (selectionPrefixString == null) { + selectionPrefixString = letter.toUpperCase(); + } else { + selectionPrefixString += letter.toUpperCase(); + } + control.getProperties().put(SELECTION_PREFIX_STRING, selectionPrefixString); + + for (T item : items) { + String string = converter.toString(item); + if (string != null && string.toUpperCase().startsWith(selectionPrefixString)) { + result = item; + break; + } + } + + ScheduledFuture<?> task = (ScheduledFuture<?>) control.getProperties().get(SELECTION_PREFIX_TASK); + if (task != null) { + task.cancel(false); + } + task = getExecutorService().schedule( + () -> control.getProperties().put(SELECTION_PREFIX_STRING, ""), 500, TimeUnit.MILLISECONDS); + control.getProperties().put(SELECTION_PREFIX_TASK, task); + + return result; + } + + private ScheduledExecutorService getExecutorService() { + if (executorService == null) { + executorService = Executors.newScheduledThreadPool(1, + runnabble -> { + Thread result = new Thread(runnabble); + result.setDaemon(true); + return result; + }); + } + return executorService; + } + + }; + + /** + * This will install an {@link EventHandler} that monitors the + * {@link KeyEvent} events to enable the "prefix selection" feature. + * The {@link EventHandler} will only be installed if the {@link ComboBox} + * is <b>not</b> editable. + * + * @param comboBox + * The {@link ComboBox} that should be customized + * + * @see PrefixSelectionCustomizer + */ + public static void customize(ComboBox<?> comboBox) { + if (!comboBox.isEditable()) { + comboBox.addEventHandler(KeyEvent.KEY_PRESSED, handler); + } + comboBox.editableProperty().addListener((o, oV, nV) -> { + if (!nV) { + comboBox.addEventHandler(KeyEvent.KEY_PRESSED, handler); + } else { + comboBox.removeEventHandler(KeyEvent.KEY_PRESSED, handler); + } + }); + } + + /** + * This will install an {@link EventHandler} that monitors the + * {@link KeyEvent} events to enable the "prefix selection" feature. + * + * @param choiceBox + * The {@link ChoiceBox} that should be customized + * + * @see PrefixSelectionCustomizer + */ + public static void customize(ChoiceBox<?> choiceBox) { + choiceBox.addEventHandler(KeyEvent.KEY_PRESSED, handler); + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/CoordinatePosition.java b/src/impl/org/controlsfx/tools/rectangle/CoordinatePosition.java new file mode 100644 index 0000000000000000000000000000000000000000..e0157013be9a9287ecb177ef1aa0e795c4a12920 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/CoordinatePosition.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle; + +/** + * Enumerates all possible positions coordinates can have relative to a rectangle. + */ +public enum CoordinatePosition { + + /** + * The coordinates are inside the rectangle. + */ + IN_RECTANGLE, + + /** + * The coordinates are outside of the rectangle. + */ + OUT_OF_RECTANGLE, + + /** + * The coordinates are close to the northern edge of the rectangle. + */ + NORTH_EDGE, + + /** + * The coordinates are close to the northern and the eastern edge of the rectangle. + */ + NORTHEAST_EDGE, + + /** + * The coordinates are close to the eastern edge of the rectangle. + */ + EAST_EDGE, + + /** + * The coordinates are close to the southern and eastern edge of the rectangle. + */ + SOUTHEAST_EDGE, + + /** + * The coordinates are close to the southern edge of the rectangle. + */ + SOUTH_EDGE, + + /** + * The coordinates are close to the southern and western edge of the rectangle. + */ + SOUTHWEST_EDGE, + + /** + * The coordinates are close to the western edge of the rectangle. + */ + WEST_EDGE, + + /** + * The coordinates are close to the northern and the western edge of the rectangle. + */ + NORTHWEST_EDGE, + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/CoordinatePositions.java b/src/impl/org/controlsfx/tools/rectangle/CoordinatePositions.java new file mode 100644 index 0000000000000000000000000000000000000000..13ec0092185b11e7d5a398cc9cb6b2eaaa69f998 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/CoordinatePositions.java @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle; + +import java.util.EnumSet; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * Computes coordinate positions relative to a rectangle. + */ +public class CoordinatePositions { + + /** + * Returns all positions the specified point has regarding the specified rectangle and its edges (using the + * specified tolerance). + * + * @param rectangle + * the rectangle relative to which the point will be checked + * @param point + * the checked point + * @param edgeTolerance + * the tolerance in pixels used to determine whether the coordinates are on some edge + * @return a set of those positions the coordinates have regarding the specified rectangle + */ + public static EnumSet<CoordinatePosition> onRectangleAndEdges( + Rectangle2D rectangle, Point2D point, double edgeTolerance) { + + EnumSet<CoordinatePosition> positions = EnumSet.noneOf(CoordinatePosition.class); + positions.add(inRectangle(rectangle, point)); + positions.add(onEdges(rectangle, point, edgeTolerance)); + return positions; + } + + /* + * RECTANGLE + */ + + /** + * Returns the position the specified coordinates have regarding the specified rectangle. Edges are not checked. + * + * @param rectangle + * the rectangle relative to which the point will be checked + * @param point + * the checked point + * @return depending on the point either {@link CoordinatePosition#IN_RECTANGLE IN_RECTANGLE} or + * {@link CoordinatePosition#OUT_OF_RECTANGLE OUT_OF_RECTANGLE} + */ + public static CoordinatePosition inRectangle(Rectangle2D rectangle, Point2D point) { + if (rectangle.contains(point)) { + return CoordinatePosition.IN_RECTANGLE; + } else { + return CoordinatePosition.OUT_OF_RECTANGLE; + } + } + + /* + * EDGES + */ + + /** + * Returns the position the specified coordinates have regarding the specified rectangle's edges using the specified + * tolerance. + * + * @param rectangle + * the rectangle relative to which the point will be checked + * @param point + * the checked point + * @param edgeTolerance + * the tolerance in pixels used to determine whether the coordinates are on some edge + * @return the edge position the coordinates have regarding the specified rectangle; the value will be null if the + * point is not near any edge + */ + public static CoordinatePosition onEdges(Rectangle2D rectangle, Point2D point, + double edgeTolerance) { + + CoordinatePosition vertical = closeToVertical(rectangle, point, edgeTolerance); + CoordinatePosition horizontal = closeToHorizontal(rectangle, point, edgeTolerance); + + return extractSingleCardinalDirection(vertical, horizontal); + } + + /** + * Returns the vertical bound the specified coordinates are closest to, if the distance is smaller than the edge + * tolerance. Otherwise, null is returned. + * + * @param rectangle + * the rectangle relative to which the point will be checked + * @param point + * the checked point + * @param edgeTolerance + * the tolerance in pixels used to determine whether the coordinates are on some edge + * @return EAST_EDGE, WEST_EDGE or null + */ + private static CoordinatePosition closeToVertical(Rectangle2D rectangle, Point2D point, double edgeTolerance) { + + double xDistanceToLeft = Math.abs(point.getX() - rectangle.getMinX()); + double xDistanceToRight = Math.abs(point.getX() - rectangle.getMaxX()); + boolean xCloseToLeft = xDistanceToLeft < edgeTolerance && xDistanceToLeft < xDistanceToRight; + boolean xCloseToRight = xDistanceToRight < edgeTolerance && xDistanceToRight < xDistanceToLeft; + + if (!xCloseToLeft && !xCloseToRight) { + return null; + } + + boolean yCloseToVertical = rectangle.getMinY() - edgeTolerance < point.getY() + && point.getY() < rectangle.getMaxY() + edgeTolerance; + if (yCloseToVertical) { + if (xCloseToLeft) { + return CoordinatePosition.WEST_EDGE; + } + if (xCloseToRight) { + return CoordinatePosition.EAST_EDGE; + } + } + + return null; + } + + /** + * Returns the horizontal bound the specified coordinates are closest to, if the distance is smaller than the edge + * tolerance. Otherwise, null is returned. + * + * @param rectangle + * the rectangle relative to which the point will be checked + * @param point + * the checked point + * @param edgeTolerance + * the tolerance in pixels used to determine whether the coordinates are on some edge + * @return NORTH_EDGE, SOUTH_EDGE or null + */ + private static CoordinatePosition closeToHorizontal(Rectangle2D rectangle, Point2D point, double edgeTolerance) { + + double yDistanceToUpper = Math.abs(point.getY() - rectangle.getMinY()); + double yDistanceToLower = Math.abs(point.getY() - rectangle.getMaxY()); + boolean yCloseToUpper = yDistanceToUpper < edgeTolerance && yDistanceToUpper < yDistanceToLower; + boolean yCloseToLower = yDistanceToLower < edgeTolerance && yDistanceToLower < yDistanceToUpper; + + if (!yCloseToUpper && !yCloseToLower) { + return null; + } + + boolean xCloseToHorizontal = rectangle.getMinX() - edgeTolerance < point.getX() + && point.getX() < rectangle.getMaxX() + edgeTolerance; + if (xCloseToHorizontal) { + if (yCloseToUpper) { + return CoordinatePosition.NORTH_EDGE; + } + if (yCloseToLower) { + return CoordinatePosition.SOUTH_EDGE; + } + } + + return null; + } + + /** + * Extracts a single cardinal direction from the two specified positions. The conditions stated below are not + * checked! + * + * @param vertical + * a vertical edge (EAST or WEST) or null + * @param horizontal + * a horizontal edge (NORTH OR SOUTH) or null + * @return the single coordinate position which matches the specified positions <br> + * (e.g. NORTH for (null, NORTH) and SOUTHWEST for (WEST, SOUTH)) + */ + private static CoordinatePosition extractSingleCardinalDirection(CoordinatePosition vertical, + CoordinatePosition horizontal) { + if (vertical == null) { + return horizontal; + } + + if (horizontal == null) { + return vertical; + } + + // north + if (horizontal == CoordinatePosition.NORTH_EDGE && vertical == CoordinatePosition.EAST_EDGE) { + return CoordinatePosition.NORTHEAST_EDGE; + } + if (horizontal == CoordinatePosition.NORTH_EDGE && vertical == CoordinatePosition.WEST_EDGE) { + return CoordinatePosition.NORTHWEST_EDGE; + } + + // south + if (horizontal == CoordinatePosition.SOUTH_EDGE && vertical == CoordinatePosition.EAST_EDGE) { + return CoordinatePosition.SOUTHEAST_EDGE; + } + if (horizontal == CoordinatePosition.SOUTH_EDGE && vertical == CoordinatePosition.WEST_EDGE) { + return CoordinatePosition.SOUTHWEST_EDGE; + } + + throw new IllegalArgumentException(); + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/Edge2D.java b/src/impl/org/controlsfx/tools/rectangle/Edge2D.java new file mode 100644 index 0000000000000000000000000000000000000000..e9f17e6fa7e67a0b91ed1cfa5ecf2f99d158865f --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/Edge2D.java @@ -0,0 +1,249 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle; + +import java.util.Objects; + +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; + +/** + * The edge of a rectangle, i.e. a vertical or horizontal line segment. + */ +public class Edge2D { + + /* + * ATTRIBUTES + */ + + /** + * The edge's center point. + */ + private final Point2D centerPoint; + + /** + * The edge's orientation. + */ + private final Orientation orientation; + + /** + * The edge's length. + */ + private final double length; + + /* + * ATTRIBUTES + */ + + /** + * Creates a new edge which is specified by its center point, orientation and length. + * + * @param centerPoint + * the edge's center point + * @param orientation + * the edge's orientation + * @param length + * the edge's length; must be non-negative. + */ + public Edge2D(Point2D centerPoint, Orientation orientation, double length) { + Objects.requireNonNull(centerPoint, "The specified center point must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(orientation, "The specified orientation must not be null."); //$NON-NLS-1$ + if (length < 0) + throw new IllegalArgumentException( + "The length must not be negative, i.e. zero or a positive value is alowed."); //$NON-NLS-1$ + + this.centerPoint = centerPoint; + this.orientation = orientation; + this.length = length; + } + + /* + * CORNERS AND DISTANCES + */ + + /** + * Returns the edge's upper left end point. It has ({@link #getLength() length} / 2) distance from the center point + * and depending on the edge's orientation either the same X (for {@link Orientation#HORIZONTAL}) or Y (for + * {@link Orientation#VERTICAL}) coordinate. + * + * @return the edge's upper left point + */ + public Point2D getUpperLeft() { + if (isHorizontal()) { + // horizontal + double cornersX = centerPoint.getX() - (length / 2); + double edgesY = centerPoint.getY(); + return new Point2D(cornersX, edgesY); + } else { + // vertical + double edgesX = centerPoint.getX(); + double cornersY = centerPoint.getY() - (length / 2); + return new Point2D(edgesX, cornersY); + } + } + + /** + * Returns the edge's lower right end point. It has ({@link #getLength() length} / 2) distance from the center point + * and depending on the edge's orientation either the same X (for {@link Orientation#HORIZONTAL}) or Y (for + * {@link Orientation#VERTICAL}) coordinate. + * + * @return the edge's lower right point + */ + public Point2D getLowerRight() { + if (isHorizontal()) { + // horizontal + double cornersX = centerPoint.getX() + (length / 2); + double edgesY = centerPoint.getY(); + return new Point2D(cornersX, edgesY); + } else { + // vertical + double edgesX = centerPoint.getX(); + double cornersY = centerPoint.getY() + (length / 2); + return new Point2D(edgesX, cornersY); + } + } + + /** + * Returns the distance of the specified point to the edge in terms of the dimension orthogonal to the edge's + * orientation. The sign denotes whether on which side of the edge, the point lies.<br> + * So e.g. if the edge is horizontal, only the Y coordinate's difference between the specified point and the edge is + * considered. If the point lies to the right of the edge, the returned value is positive. + * + * @param otherPoint + * the point to where the distance is computed + * @return the distance + */ + public double getOrthogonalDifference(Point2D otherPoint) { + Objects.requireNonNull(otherPoint, "The other point must nt be null."); //$NON-NLS-1$ + + if (isHorizontal()) + // horizontal -> subtract y coordinates + return otherPoint.getY() - centerPoint.getY(); + else + // vertical-> subtract x coordinates + return otherPoint.getX() - centerPoint.getX(); + } + + /* + * ATTRIBUTE ACCESS + */ + + /** + * @return the edge's center point + */ + public Point2D getCenterPoint() { + return centerPoint; + } + + /** + * Returns this edge's orientation. Note that the orientation can also be checked with {@link #isHorizontal()} and + * {@link #isVertical()}. + * + * @return the edge's orientation + */ + public Orientation getOrientation() { + return orientation; + } + + /** + * Indicates whether this is a {@link Orientation#HORIZONTAL horizontal} edge. + * + * @return true if {@link #getOrientation()} returns {@link Orientation#HORIZONTAL} + */ + public boolean isHorizontal() { + return orientation == Orientation.HORIZONTAL; + } + + /** + * Indicates whether this is a {@link Orientation#VERTICAL horizontal} edge. + * + * @return true if {@link #getOrientation()} returns {@link Orientation#VERTICAL} + */ + public boolean isVertical() { + return orientation == Orientation.VERTICAL; + } + + /** + * @return the edge's length + */ + public double getLength() { + return length; + } + + /* + * EQUALS, HASHCODE & TOSTRING + */ + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((centerPoint == null) ? 0 : centerPoint.hashCode()); + long temp; + temp = Double.doubleToLongBits(length); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + ((orientation == null) ? 0 : orientation.hashCode()); + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Edge2D other = (Edge2D) obj; + if (centerPoint == null) { + if (other.centerPoint != null) + return false; + } else if (!centerPoint.equals(other.centerPoint)) + return false; + if (Double.doubleToLongBits(length) != Double.doubleToLongBits(other.length)) + return false; + if (orientation != other.orientation) + return false; + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "Edge2D [centerX = " + centerPoint.getX() + ", centerY = " + centerPoint.getY() //$NON-NLS-1$ //$NON-NLS-2$ + + ", orientation = " + orientation + ", length = " + length + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/Rectangles2D.java b/src/impl/org/controlsfx/tools/rectangle/Rectangles2D.java new file mode 100644 index 0000000000000000000000000000000000000000..80f5d9062b9634e7c57c8ea770d673fbc9226ba2 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/Rectangles2D.java @@ -0,0 +1,796 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle; + +import impl.org.controlsfx.tools.MathTools; + +import java.util.Objects; + +import javafx.geometry.Bounds; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * Usability methods for rectangles. + */ +public class Rectangles2D { + + /* + * CHECKS + */ + + /** + * Indicates whether the specified rectangle contains the specified edge. + * + * @param rectangle + * the rectangle to check + * @param edge + * the edge to check + * @return {@code true} if both end points of the edge are {@link Rectangle2D#contains(Point2D) contained} in the + * rectangle + */ + public static boolean contains(Rectangle2D rectangle, Edge2D edge) { + Objects.requireNonNull(rectangle, "The argument 'rectangle' must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(edge, "The argument 'edge' must not be null."); //$NON-NLS-1$ + + boolean edgeInBounds = rectangle.contains(edge.getUpperLeft()) && rectangle.contains(edge.getLowerRight()); + return edgeInBounds; + } + + /* + * POINT + */ + + /** + * Moves the specified point into the specified rectangle. If the point is already with the rectangle, it is + * returned. Otherwise the point in the rectangle which is closest to the specified one is returned. + * + * @param rectangle + * the {@link Rectangle2D} into which the point should be moved + * @param point + * the {@link Point2D} which is checked + * @return either the specified {@code point} or the {@link Point2D} which is closest to it while still being + * contained on the {@code rectangle} + */ + public static Point2D inRectangle(Rectangle2D rectangle, Point2D point) { + Objects.requireNonNull(rectangle, "The argument 'rectangle' must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(point, "The argument 'point' must not be null."); //$NON-NLS-1$ + + if (rectangle.contains(point)) { + return point; + } + + // force the x and y coordinate into the rectangle + double newX = MathTools.inInterval(rectangle.getMinX(), point.getX(), rectangle.getMaxX()); + double newY = MathTools.inInterval(rectangle.getMinY(), point.getY(), rectangle.getMaxY()); + return new Point2D(newX, newY); + } + + /** + * Returns the center of the specified rectangle as a point. + * + * @param rectangle + * the {@link Rectangle2D} whose center point will be returned + * @return the {@link Point2D} whose x/y coordinates lie at {@code (min + max) / 2}. + */ + public static Point2D getCenterPoint(Rectangle2D rectangle) { + Objects.requireNonNull(rectangle, "The argument 'rectangle' must not be null."); //$NON-NLS-1$ + + double centerX = (rectangle.getMinX() + rectangle.getMaxX()) / 2; + double centerY = (rectangle.getMinY() + rectangle.getMaxY()) / 2; + return new Point2D(centerX, centerY); + } + + /* + * OTHER RECTANGLE + */ + + /** + * Returns the rectangle which represents the intersection of the two specified rectangles. + * + * @param a + * a {@link Rectangle2D} + * @param b + * another {@link Rectangle2D} + * @return a {@link Rectangle2D} which is the intersection of {@code a} and {@code b}; possible + * {@link Rectangle2D#EMPTY}. + */ + public static Rectangle2D intersection(Rectangle2D a, Rectangle2D b) { + Objects.requireNonNull(a, "The argument 'a' must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(b, "The argument 'b' must not be null."); //$NON-NLS-1$ + + if (a.intersects(b)) { + double intersectionMinX = Math.max(a.getMinX(), b.getMinX()); + double intersectionMaxX = Math.min(a.getMaxX(), b.getMaxX()); + double intersectionWidth = intersectionMaxX - intersectionMinX; + double intersectionMinY = Math.max(a.getMinY(), b.getMinY()); + double intersectionMaxY = Math.min(a.getMaxY(), b.getMaxY()); + double intersectionHeight = intersectionMaxY - intersectionMinY; + return new Rectangle2D(intersectionMinX, intersectionMinY, intersectionWidth, intersectionHeight); + } else { + return Rectangle2D.EMPTY; + } + } + + /* + * TWO CORNERS + */ + + /** + * Creates a new rectangle with the two specified corners. The two corners will be interpreted as being diagonal of + * each other. + * + * @param oneCorner + * one corner + * @param diagonalCorner + * another corner, diagonal from the first + * @return the {@link Rectangle2D} which is defined by the two corners + */ + public static Rectangle2D forDiagonalCorners(Point2D oneCorner, Point2D diagonalCorner) { + Objects.requireNonNull(oneCorner, "The specified corner must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(diagonalCorner, "The specified diagonal corner must not be null."); //$NON-NLS-1$ + + double minX = Math.min(oneCorner.getX(), diagonalCorner.getX()); + double minY = Math.min(oneCorner.getY(), diagonalCorner.getY()); + double width = Math.abs(oneCorner.getX() - diagonalCorner.getX()); + double height = Math.abs(oneCorner.getY() - diagonalCorner.getY()); + + return new Rectangle2D(minX, minY, width, height); + } + + /* + * CORNER AND SIZE + */ + + /** + * Creates a new rectangle with the specified {@code upperLeft} corner and the specified {@code width} and + * {@code height}. + * + * @param upperLeft + * one corner + * @param width + * the new rectangle's width + * @param height + * the new rectangle's height + * @return the {@link Rectangle2D} which is defined by the specified upper left corner and width and height + */ + public static Rectangle2D forUpperLeftCornerAndSize(Point2D upperLeft, double width, double height) { + return new Rectangle2D(upperLeft.getX(), upperLeft.getY(), width, height); + } + + /* + * CORNER AND RATIO + */ + + /** + * Creates a new rectangle with the two specified corners. The two corners will be interpreted as being diagonal of + * each other. The returned rectangle will have the specified {@code fixedCorner} as its corner. The other one will + * either be on the same x- or y-parallel as the {@code diagonalCorner} but will be such that the rectangle has the + * specified {@code ratio}. + * + * @param fixedCorner + * one corner + * @param diagonalCorner + * another corner, diagonal from the first + * @param ratio + * the ratio the returned rectangle must have; must be non-negative + * @return the {@link Rectangle2D} which is defined by the {@code fixedCorner}, the x- or y-parallel of the + * {@code diagonalCorner} and the {@code ratio} + */ + public static Rectangle2D forDiagonalCornersAndRatio(Point2D fixedCorner, Point2D diagonalCorner, double ratio) { + Objects.requireNonNull(fixedCorner, "The specified fixed corner must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(diagonalCorner, "The specified diagonal corner must not be null."); //$NON-NLS-1$ + if (ratio < 0) { + throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // the coordinate differences - note that they can be negative + double xDifference = diagonalCorner.getX() - fixedCorner.getX(); + double yDifference = diagonalCorner.getY() - fixedCorner.getY(); + + // the following calls will only change one of the two differences: + // the one whose value is too large compared to what it should be based on the other difference and the ratio; + // its value will instead be the other difference time or divided by the ratio + double xDifferenceByRatio = correctCoordinateDifferenceByRatio(xDifference, yDifference, ratio); + double yDifferenceByRatio = correctCoordinateDifferenceByRatio(yDifference, xDifference, 1 / ratio); + + // these are the coordinates of the upper left corner of the future rectangle + double minX = getMinCoordinate(fixedCorner.getX(), xDifferenceByRatio); + double minY = getMinCoordinate(fixedCorner.getY(), yDifferenceByRatio); + + double width = Math.abs(xDifferenceByRatio); + double height = Math.abs(yDifferenceByRatio); + + return new Rectangle2D(minX, minY, width, height); + } + + /** + * Returns the difference with the following properties:<br> + * - it has the same sign as the specified difference <br> + * - its absolute value is the minimum of the absolute values of ...<br> + * ... the specified difference and <br> + * ... the product of the specified ratio and the other specified difference <br> + * + * @param difference + * the difference to check + * @param otherDifference + * the other difference + * @param ratioAsMultiplier + * the ratio as a multiplier for the other difference + * @return the corrected difference + */ + private static double correctCoordinateDifferenceByRatio(double difference, double otherDifference, + double ratioAsMultiplier) { + double differenceByRatio = otherDifference * ratioAsMultiplier; + double correctedDistance = Math.min(Math.abs(difference), Math.abs(differenceByRatio)); + + return correctedDistance * Math.signum(difference); + } + + /** + * Returns the minimum coordinate such that a rectangle starting from that coordinate will contain the fixed + * coordinate as a corner. + * + * @param fixedCoordinate + * the coordinate which must be a corner + * @param difference + * the difference in the computed coordinate + * @return fixedCoordinate + difference; if difference < 0 <br> + * fixedCoordinate; else + */ + private static double getMinCoordinate(double fixedCoordinate, double difference) { + if (difference < 0) { + return fixedCoordinate + difference; + } + + return fixedCoordinate; + } + + /* + * CENTER AND SIZE + */ + + /** + * Creates a new rectangle with the specified center and the specified width and height. + * + * @param centerPoint + * the center point o the new rectangle + * @param width + * the width of the new rectangle + * @param height + * the height of the new rectangle + * @return a rectangle with the specified center and size + */ + public static Rectangle2D forCenterAndSize(Point2D centerPoint, double width, double height) { + Objects.requireNonNull(centerPoint, "The specified center point must not be null."); //$NON-NLS-1$ + + double absoluteWidth = Math.abs(width); + double absoluteHeight = Math.abs(height); + double minX = centerPoint.getX() - absoluteWidth / 2; + double minY = centerPoint.getY() - absoluteHeight / 2; + + return new Rectangle2D(minX, minY, width, height); + } + + /* + * ORIGINAL, AREA AND RATIO + */ + + /** + * Creates a new rectangle with the same center point and area as the specified {@code original} rectangle with the + * specified {@code ratio}. + * + * @param original + * the original rectangle + * @param ratio + * the new ratio + * @return a new {@link Rectangle2D} with the same center point as the {@code original} and the specified + * {@code ratio}; it has the same area as the {@code original} + * @throws NullPointerException + * if the {@code original} rectangle is null + */ + public static Rectangle2D fixRatio(Rectangle2D original, double ratio) { + Objects.requireNonNull(original, "The specified original rectangle must not be null."); //$NON-NLS-1$ + if (ratio < 0) { + throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return createWithFixedRatioWithinBounds(original, ratio, null); + } + + /** + * Creates a new rectangle with the same center point and area (if possible) as the specified {@code original} + * rectangle with the specified {@code ratio} and respecting the specified {@code bounds}. + * + * @param original + * the original rectangle + * @param ratio + * the new ratio + * @param bounds + * the bounds within which the new rectangle will be located + * @return a new {@link Rectangle2D} with the same center point as the {@code original} and the specified + * {@code ratio}; it has the same area as the {@code original} unless this would violate the bounds; in this + * case it is as large as possible while still staying within the bounds + * @throws NullPointerException + * if the {@code original} or {@code bounds} rectangle is null + * @throws IllegalArgumentException + * if the {@code original} rectangle's center point is out of the bounds + */ + public static Rectangle2D fixRatioWithinBounds(Rectangle2D original, double ratio, Rectangle2D bounds) { + Objects.requireNonNull(original, "The specified original rectangle must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(bounds, "The specified bounds for the new rectangle must not be null."); //$NON-NLS-1$ + if (ratio < 0) { + throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return createWithFixedRatioWithinBounds(original, ratio, bounds); + } + + /** + * Creates a new rectangle with the same center point and area as the specified {@code original} rectangle with the + * specified {@code ratio} and respecting the specified {@code bounds} (if not-{@code null}). + * + * @param original + * the original rectangle + * @param ratio + * the new ratio + * @param bounds + * the bounds within which the new rectangle will be located; might be {@code null} + * @return a new {@link Rectangle2D} with the same center point as the {@code original} and the specified + * {@code ratio}; it has the same area as the {@code original} unless this would violate the bounds; in this + * case it is as large as possible while still staying within the bounds + * @throws IllegalArgumentException + * if the {@code original} rectangle's center point is out of the {@code bounds} + */ + private static Rectangle2D createWithFixedRatioWithinBounds(Rectangle2D original, double ratio, Rectangle2D bounds) { + Point2D centerPoint = getCenterPoint(original); + + boolean centerPointInBounds = bounds == null || bounds.contains(centerPoint); + if (!centerPointInBounds) { + throw new IllegalArgumentException("The center point " + centerPoint //$NON-NLS-1$ + + " of the original rectangle is out of the specified bounds."); //$NON-NLS-1$ + } + + double area = original.getWidth() * original.getHeight(); + + return createForCenterAreaAndRatioWithinBounds(centerPoint, area, ratio, bounds); + } + + /* + * CENTER, AREA AND RATIO + */ + + /** + * Creates a new rectangle with the specified {@code centerPoint}, {@code area} and {@code ratio}. + * + * @param centerPoint + * the new rectangle's center point + * @param area + * the new rectangle's area + * @param ratio + * the new ratio + * @return a new {@link Rectangle2D} with the specified {@code centerPoint}, {@code area} and {@code ratio} + * @throws IllegalArgumentException + * if the {@code centerPoint} is out of the {@code bounds} + */ + public static Rectangle2D forCenterAndAreaAndRatio(Point2D centerPoint, double area, double ratio) { + Objects.requireNonNull(centerPoint, "The specified center point of the new rectangle must not be null."); //$NON-NLS-1$ + if (area < 0) { + throw new IllegalArgumentException("The specified area " + area + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (ratio < 0) { + throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return createForCenterAreaAndRatioWithinBounds(centerPoint, area, ratio, null); + } + + /** + * Creates a new rectangle with the specified {@code centerPoint}, {@code area} (if possible) and {@code ratio}, + * respecting the specified {@code bounds}. + * + * @param centerPoint + * the new rectangle's center point + * @param area + * the new rectangle's area (if possible without violating the bounds) + * @param ratio + * the new ratio + * @param bounds + * the bounds within which the new rectangle will be located + * @return a new {@link Rectangle2D} with the specified {@code centerPoint} and {@code ratio}; it has the specified + * {@code area} unless this would violate the {@code bounds}; in this case it is as large as possible while + * still staying within the bounds + * @throws IllegalArgumentException + * if the {@code centerPoint} is out of the {@code bounds} + */ + public static Rectangle2D forCenterAndAreaAndRatioWithinBounds( + Point2D centerPoint, double area, double ratio, Rectangle2D bounds) { + + Objects.requireNonNull(centerPoint, "The specified center point of the new rectangle must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(bounds, "The specified bounds for the new rectangle must not be null."); //$NON-NLS-1$ + boolean centerPointInBounds = bounds.contains(centerPoint); + if (!centerPointInBounds) { + throw new IllegalArgumentException("The center point " + centerPoint //$NON-NLS-1$ + + " of the original rectangle is out of the specified bounds."); //$NON-NLS-1$ + } + if (area < 0) { + throw new IllegalArgumentException("The specified area " + area + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (ratio < 0) { + throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return createForCenterAreaAndRatioWithinBounds(centerPoint, area, ratio, bounds); + } + + /** + * Creates a new rectangle with the specified {@code centerPoint}, {@code area} (if possible) and {@code ratio}, + * respecting the specified {@code bounds} (if not-null). + * + * @param centerPoint + * the new rectangle's center point + * @param area + * the new rectangle's area (if possible without violating the bounds) + * @param ratio + * the new ratio + * @param bounds + * the bounds within which the new rectangle will be located + * @return a new {@link Rectangle2D} with the specified {@code centerPoint} and {@code ratio}; it has the specified + * {@code area} unless this would violate the {@code bounds}; in this case it is as large as possible while + * still staying within the bounds + * @throws IllegalArgumentException + * if the {@code centerPoint} is out of the {@code bounds} + */ + private static Rectangle2D createForCenterAreaAndRatioWithinBounds( + Point2D centerPoint, double area, double ratio, Rectangle2D bounds) { + + double newWidth = Math.sqrt(area * ratio); + double newHeight = area / newWidth; + + boolean boundsSpecified = bounds != null; + if (boundsSpecified) { + double reductionFactor = lengthReductionToStayWithinBounds(centerPoint, newWidth, newHeight, bounds); + newWidth *= reductionFactor; + newHeight *= reductionFactor; + } + + return Rectangles2D.forCenterAndSize(centerPoint, newWidth, newHeight); + } + + /** + * Computes the factor by which the specified width and height must be multiplied to keep a rectangle with their + * ratio and the specified center point within the specified bounds. + * + * @param centerPoint + * the center point of the new rectangle + * @param width + * the original width which might be too large + * @param height + * the original height which might be too large + * @param bounds + * the bounds within which the new rectangle will be located + * @return the factor with which the width and height must be multiplied to stay within the bounds; always in the + * closed interval [0; 1] + * @throws IllegalArgumentException + * if the {@code centerPoint} is out of the {@code bounds}; if {@code width} or {@code height} are not + * larger than zero + */ + private static double lengthReductionToStayWithinBounds( + Point2D centerPoint, double width, double height, Rectangle2D bounds) { + + Objects.requireNonNull(centerPoint, "The specified center point of the new rectangle must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(bounds, "The specified bounds for the new rectangle must not be null."); //$NON-NLS-1$ + boolean centerPointInBounds = bounds.contains(centerPoint); + if (!centerPointInBounds) { + throw new IllegalArgumentException("The center point " + centerPoint //$NON-NLS-1$ + + " of the original rectangle is out of the specified bounds."); //$NON-NLS-1$ + } + if (width < 0) { + throw new IllegalArgumentException("The specified width " + width + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (height < 0) { + throw new IllegalArgumentException("The specified height " + height + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /* + * Compute the center point's distance to all edges. The width and height must be reduced (by the returned + * factor) such that their halves (!) are not greater than those distances. This can be done by finding the + * minimum ratio between the distance and the halved width/height. + */ + + double distanceToEast = Math.abs(centerPoint.getX() - bounds.getMinX()); + double distanceToWest = Math.abs(centerPoint.getX() - bounds.getMaxX()); + double distanceToNorth = Math.abs(centerPoint.getY() - bounds.getMinY()); + double distanceToSouth = Math.abs(centerPoint.getY() - bounds.getMaxY()); + + // the returned factor must not be greater than one; otherwise the size would increase + return MathTools.min(1, + distanceToEast / width * 2, distanceToWest / width * 2, + distanceToNorth / height * 2, distanceToSouth / height * 2); + } + + /* + * EDGES + */ + + /** + * Returns a rectangle that has the specified edge and has its opposing edge on the parallel axis defined by the + * specified point's X or Y coordinate (depending on the edge's orientation). + * + * @param edge + * the edge which will be contained in the returned rectangle + * @param point + * the point whose X or Y coordinate defines the other edge + * @return a rectangle + */ + public static Rectangle2D forEdgeAndOpposingPoint(Edge2D edge, Point2D point) { + double otherDimension = edge.getOrthogonalDifference(point); + return createForEdgeAndOtherDimension(edge, otherDimension); + } + + /** + * Returns a rectangle that is principally defined by the specified edge and point. It should have the specified + * edge as one of its own and its parallel edge should contain the point. While this would already well-define the + * rectangle (compare {@link #forEdgeAndOpposingPoint(Edge2D, Point2D) forEdgeAndOpposingPoint}) the additionally + * specified ratio and bounds have precedence over these arguments:<br> + * The returned rectangle will have the ratio and will be within the bounds. If the bounds make it possible, the + * specified point will lie on the edge parallel to the specified one. In order to maintain the ratio, this will + * make it necessary to not use the specified edge but instead one with a different length. The new edge will have + * the same center point as the specified one.<br> + * This results on the following behavior: As the point is moved closer to or further away from the edge, the + * resulting rectangle shrinks and grows while being anchored to the specified edge's center point and keeping the + * ratio. This is limited by the bounds. + * + * @param edge + * the edge which defines the center point and orientation of one of the rectangle's edges; must be + * within the specified {@code bounds} + * @param point + * the point to which the rectangle spans if ratio and bounds allow it + * @param ratio + * the ratio the new rectangle must have + * @param bounds + * the bounds within which the new rectangle must lie + * @return a rectangle + */ + public static Rectangle2D forEdgeAndOpposingPointAndRatioWithinBounds( + Edge2D edge, Point2D point, double ratio, Rectangle2D bounds) { + + Objects.requireNonNull(edge, "The specified edge must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(point, "The specified point must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(bounds, "The specified bounds must not be null."); //$NON-NLS-1$ + + boolean edgeInBounds = contains(bounds, edge); + if (!edgeInBounds) { + throw new IllegalArgumentException( + "The specified edge " + edge + " is not entirely contained on the specified bounds."); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (ratio < 0) { + throw new IllegalArgumentException("The specified ratio " + ratio + " must be larger than zero."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /* + * 1. move the point into the bounds + * 2. create an edge whose length matches the distance to the point and the ratio + * 3. correct that edge so that it lies within the bounds + * 4. create a new rectangle from that edge and the ratio + */ + + Point2D boundedPoint = movePointIntoBounds(point, bounds); + Edge2D unboundedEdge = resizeEdgeForDistanceAndRatio(edge, boundedPoint, ratio); + Edge2D boundedEdge = resizeEdgeForBounds(unboundedEdge, bounds); + + // when computing the other dimension, note that the sign of the original difference between edge and point is + // important; otherwise the "direction" of the resize is wrong + double otherDimension = Math.signum(boundedEdge.getOrthogonalDifference(boundedPoint)); + if (boundedEdge.isHorizontal()) { + // edge horizontal -> width fixed -> use length to compute height + otherDimension *= boundedEdge.getLength() / ratio; + } else { + // edge vertical -> height fixed -> use length to compute width + otherDimension *= boundedEdge.getLength() * ratio; + } + + return createForEdgeAndOtherDimension(boundedEdge, otherDimension); + } + + /** + * Returns either the specified point if if the specified bounds {@link Rectangle2D#contains(Point2D) contain} it or + * a point whose X and/or Y coordinates are moved into the bounds. + * + * @param point + * the point to move into the {@code bounds} + * @param bounds + * the bounds into which the {@code point} will be moved + * @return either {@code point} or a new {@link Point2D} whose coordinates were changed so that it lies within the + * {@code bounds} + */ + private static Point2D movePointIntoBounds(Point2D point, Rectangle2D bounds) { + if (bounds.contains(point)) { + return point; + } else { + double boundedPointX = MathTools.inInterval(bounds.getMinX(), point.getX(), bounds.getMaxX()); + double boundedPointY = MathTools.inInterval(bounds.getMinY(), point.getY(), bounds.getMaxY()); + return new Point2D(boundedPointX, boundedPointY); + } + } + + /** + * Returns an edge with the same center point and orientation as the specified edge. Its length has the specified + * ratio to the distance of the edge and the specified point. + * + * @param edge + * the edge whose center point and orientation defines the returned edge's center point and orientation + * @param point + * the point to which the distance is measured + * @param ratio + * the ratio between the distance to the {@code point} and the returned edge's length + * @return an {@link Edge2D} + */ + private static Edge2D resizeEdgeForDistanceAndRatio(Edge2D edge, Point2D point, double ratio) { + double distance = Math.abs(edge.getOrthogonalDifference(point)); + if (edge.isHorizontal()) { + // a horizontal edge's length lies in the X axis; the distance lies in the Y axis: x = y * ratio + double xLength = distance * ratio; + return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), xLength); + } else { + // a vertical edge's length lies in the Y axis; the distance lies in the X axis: y = x / ratio + double yLength = distance / ratio; + return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), yLength); + } + } + + /** + * Returns an edge with the same center point and orientation as the specified edge. If necessary, its length is + * reduced to fit within the bounds. + * + * @param edge + * the edge whose center point and orientation defines the returned edge's center point and orientation; + * the center point must be within the bounds or an {@link IllegalArgumentException} will be thrown + * @param bounds + * the bounds within which the returned edge must be contained + * @return either the specified {@code edge} if it is with in the {@code bounds} or one with a corrected length + * @throws IllegalArgumentException + * if the {@code edge}'s center point is out of {@code bounds} + */ + private static Edge2D resizeEdgeForBounds(Edge2D edge, Rectangle2D bounds) { + // return the same edge if it is in the bounds + boolean edgeInBounds = contains(bounds, edge); + if (edgeInBounds) { + return edge; + } + + // make sure the bounds contain the edge's center point + boolean centerPointInBounds = bounds.contains(edge.getCenterPoint()); + if (!centerPointInBounds) { + throw new IllegalArgumentException( + "The specified edge's center point (" + edge + ") is out of the specified bounds (" + bounds + ")."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + if (edge.isHorizontal()) { + // compute the length bounds for the left and right part of the edge + double leftPartLengthBound = Math.abs(bounds.getMinX() - edge.getCenterPoint().getX()); + double rightPartLengthBound = Math.abs(bounds.getMaxX() - edge.getCenterPoint().getX()); + // compute the length of the left and right parts of the edge + double leftPartLength = MathTools.inInterval(0, edge.getLength() / 2, leftPartLengthBound); + double rightPartLength = MathTools.inInterval(0, edge.getLength() / 2, rightPartLengthBound); + // compute the total length as double of the smaller length + double horizontalLength = Math.min(leftPartLength, rightPartLength) * 2; + return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), horizontalLength); + } else { + // compute the length bounds for the lower and upper part of the edge + double lowerPartLengthBound = Math.abs(bounds.getMinY() - edge.getCenterPoint().getY()); + double upperPartLengthBound = Math.abs(bounds.getMaxY() - edge.getCenterPoint().getY()); + // compute the length of the lower and upper part of the edge + double lowerPartLength = MathTools.inInterval(0, edge.getLength() / 2, lowerPartLengthBound); + double upperPartLength = MathTools.inInterval(0, edge.getLength() / 2, upperPartLengthBound); + // compute the total length as double of the smaller length + double verticalLength = Math.min(lowerPartLength, upperPartLength) * 2; + return new Edge2D(edge.getCenterPoint(), edge.getOrientation(), verticalLength); + } + } + + /** + * Returns a rectangle that has the specified edge and a height or width (depending on the edge's orientation) as + * specified by {@code otherDimension}. + * + * @param edge + * the edge which will be contained in the returned rectangle + * @param otherDimension + * if the edge's orientation is {@link Orientation#HORIZONTAL horizontal}, this is interpreted as the + * height; if the edge's orientation is {@link Orientation#VERTICAL vertical}, this is interpreted as the + * width + * @return a rectangle + */ + private static Rectangle2D createForEdgeAndOtherDimension(Edge2D edge, double otherDimension) { + if (edge.isHorizontal()) { + return createForHorizontalEdgeAndHeight(edge, otherDimension); + } else { + return createForVerticalEdgeAndWidth(edge, otherDimension); + } + } + + /** + * Returns a rectangle that has the specified horizontal edge and height. Depending on whether the width is positive + * or negative, the specified edge will be the upper or lower edge of the returned rectangle. + * + * @param horizontalEdge + * the horizontal edge which will be contained in the returned rectangle + * @param height + * the returned rectangle's height + * @return a rectangle + */ + private static Rectangle2D createForHorizontalEdgeAndHeight(Edge2D horizontalEdge, double height) { + Point2D leftEdgeEndPoint = horizontalEdge.getUpperLeft(); + double upperLeftX = leftEdgeEndPoint.getX(); + // if the height is negative, reduce the Y coordinate by that amount + double upperLeftY = leftEdgeEndPoint.getY() + Math.min(0, height); + + double absoluteWidth = Math.abs(horizontalEdge.getLength()); + double absoluteHeight = Math.abs(height); + + return new Rectangle2D(upperLeftX, upperLeftY, absoluteWidth, absoluteHeight); + } + + /** + * Returns a rectangle that has the specified horizontal edge and width. Depending on whether the width is positive + * or negative, the specified edge will be the left or right edge of the returned rectangle. + * + * @param verticalEdge + * the vertical edge which will be contained in the returned rectangle + * @param width + * the returned rectangle's height + * @return a rectangle + */ + private static Rectangle2D createForVerticalEdgeAndWidth(Edge2D verticalEdge, double width) { + Point2D upperEdgeEndPoint = verticalEdge.getUpperLeft(); + // if the width is negative, reduce the X coordinate by that amount + double upperLeftX = upperEdgeEndPoint.getX() + Math.min(0, width); + double upperLeftY = upperEdgeEndPoint.getY(); + + double absoluteWidth = Math.abs(width); + double absoluteHeight = Math.abs(verticalEdge.getLength()); + + return new Rectangle2D(upperLeftX, upperLeftY, absoluteWidth, absoluteHeight); + } + + /* + * MISC + */ + + /** + * Returns a rectangle with the same coordinates as the specified bounds. + * + * @param bounds + * the {@link Bounds} for which the rectangle will be created + * @return a {@link Rectangle2D} with the same minX-, minY-, maxX- and maxY-coordiantes as the specified bounds + */ + public static Rectangle2D fromBounds(Bounds bounds) { + return new Rectangle2D(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight()); + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/AbstractBeginEndCheckingChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/AbstractBeginEndCheckingChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..61e0e8238cc7301359f4b8098bc1c9dbc339cd9d --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/AbstractBeginEndCheckingChangeStrategy.java @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import java.util.Objects; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * Abstract superclass to implementations of {@link Rectangle2DChangeStrategy}. Checks whether the specified points are not-null + * and the "begin-continue-end"-contract. + */ +abstract class AbstractBeginEndCheckingChangeStrategy implements Rectangle2DChangeStrategy { + + // ATTRIBUTES + + /** + * Indicates whether {@link #beginChange(Point2D) beginChange} was called. + */ + private boolean beforeBegin; + + // CONSTRUCTOR + + /** + * Creates a change strategy which checks whether begin and end are correctly called. + */ + protected AbstractBeginEndCheckingChangeStrategy() { + beforeBegin = true; + } + + // IMPLEMENTATION OF 'ChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + public final Rectangle2D beginChange(Point2D point) { + Objects.requireNonNull(point, "The specified point must not be null."); //$NON-NLS-1$ + if (!beforeBegin) + throw new IllegalStateException( + "The change already began, so 'beginChange' must not be called again before 'endChange' was called."); //$NON-NLS-1$ + beforeBegin = false; + + beforeBeginHook(point); + return doBegin(point); + } + + /** + * {@inheritDoc} + */ + @Override + public final Rectangle2D continueChange(Point2D point) { + Objects.requireNonNull(point, "The specified point must not be null."); //$NON-NLS-1$ + if (beforeBegin) + throw new IllegalStateException("The change did not begin. Call 'beginChange' before 'continueChange'."); //$NON-NLS-1$ + + return doContinue(point); + } + + /** + * {@inheritDoc} + */ + @Override + public final Rectangle2D endChange(Point2D point) { + Objects.requireNonNull(point, "The specified point must not be null."); //$NON-NLS-1$ + if (beforeBegin) + throw new IllegalStateException("The change did not begin. Call 'beginChange' before 'endChange'."); //$NON-NLS-1$ + + Rectangle2D finalRectangle = doEnd(point); + afterEndHook(point); + beforeBegin = true; + return finalRectangle; + } + + //ABSTRACT METHODS + + /** + * Called before the change begins at the specified point. + * + * @param point + * a point + */ + protected void beforeBeginHook(Point2D point) { + // can be overridden by subclasses + } + + /** + * Begins the change at the specified point. + * + * @param point + * a point + * @return the new rectangle + */ + protected abstract Rectangle2D doBegin(Point2D point); + + /** + * Continues the change to the specified point. Must not be called before a call to {@link #beginChange}. + * + * @param point + * a point + * @return the new rectangle + */ + protected abstract Rectangle2D doContinue(Point2D point); + + /** + * Ends the change at the specified point. Must not be called before a call to {@link #beginChange}. + * + * @param point + * a point + * @return the new rectangle + */ + protected abstract Rectangle2D doEnd(Point2D point); + + /** + * Called after the change ends at the specified point. + * + * @param point + * a point + */ + protected void afterEndHook(Point2D point) { + // can be overridden by subclasses + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/AbstractFixedEdgeChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/AbstractFixedEdgeChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..ccc501941f6d49b0ddc427cd527995bba7cbee78 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/AbstractFixedEdgeChangeStrategy.java @@ -0,0 +1,137 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.rectangle.Edge2D; +import impl.org.controlsfx.tools.rectangle.Rectangles2D; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * Abstract superclass to those implementations of {@link Rectangle2DChangeStrategy} which compute their rectangle by + * spanning it from a fixed edge to the parallel edge defined by the point given to + * {@link Rectangle2DChangeStrategy#continueChange(Point2D) continueChange}. <br> + * The edge is fixed during the change but can be changed in between changes. Implemented such that a ratio is respected + * if specified. + */ +abstract class AbstractFixedEdgeChangeStrategy extends AbstractRatioRespectingChangeStrategy { + + // ATTRIBUTES + + /** + * A rectangle which defines the bounds within which the new rectangle must be contained. + */ + private final Rectangle2D bounds; + + /** + * The edge which is fixed during the change. In {@link #doBegin(Point2D)} it is set to {@link #getFixedEdge()}; in + * {@link #doEnd(Point2D)} it is set to {@code null}. + */ + private Edge2D fixedEdge; + + // CONSTRUCTOR + + /** + * Creates a fixed edge change strategy. It respects the specified {@code ratio} if {@code ratioFixed} is + * {@code true}. + * + * @param ratioFixed + * indicates whether the ratio will be fixed + * @param ratio + * defines the fixed ratio + * @param bounds + * the bounds within which the new rectangle must be contained + */ + protected AbstractFixedEdgeChangeStrategy(boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio); + this.bounds = bounds; + } + + // ABSTRACT METHODS + + /** + * Returns the edge which is fixed during the change. Called once when the change begins. + * + * @return the edge which is fixed during the change + */ + protected abstract Edge2D getFixedEdge(); + + // IMPLEMENTATION OF 'do...' + + /** + * Creates a new rectangle from the two edges defined by {@link #fixedEdge} and its parallel through the specified + * point. + * + * @param point + * the point defining the parallel edge + * @return the rectangle defined the two edges + */ + private final Rectangle2D createFromEdges(Point2D point) { + Point2D pointInBounds = Rectangles2D.inRectangle(bounds, point); + + if (isRatioFixed()) { + return Rectangles2D.forEdgeAndOpposingPointAndRatioWithinBounds( + fixedEdge, pointInBounds, getRatio(), bounds); + } else { + return Rectangles2D.forEdgeAndOpposingPoint(fixedEdge, pointInBounds); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected final Rectangle2D doBegin(Point2D point) { + boolean startPointNotInBounds = !bounds.contains(point); + if (startPointNotInBounds) { + throw new IllegalArgumentException( + "The change's start point (" + point + ") must lie within the bounds (" + bounds + ")."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + fixedEdge = getFixedEdge(); + return createFromEdges(point); + } + + /** + * {@inheritDoc} + */ + @Override + protected Rectangle2D doContinue(Point2D point) { + return createFromEdges(point); + } + + /** + * {@inheritDoc} + */ + @Override + protected final Rectangle2D doEnd(Point2D point) { + Rectangle2D newRectangle = createFromEdges(point); + fixedEdge = null; + return newRectangle; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/AbstractFixedPointChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/AbstractFixedPointChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..27353e8abc7b40ebde80f7b4f63b1a1838bdf127 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/AbstractFixedPointChangeStrategy.java @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.rectangle.Rectangles2D; + +import java.util.Objects; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * Abstract superclass to those implementations of {@link Rectangle2DChangeStrategy} which compute their rectangle by + * spanning it from a fixed point to the point given to {@link Rectangle2DChangeStrategy#continueChange(Point2D) + * continueChange}. <br> + * The point is fixed during the change but can be changed in between changes. Implemented such that a ratio is + * respected if specified. + */ +abstract class AbstractFixedPointChangeStrategy extends AbstractRatioRespectingChangeStrategy { + + // ATTRIBUTES + + /** + * A rectangle which defines the bounds within which the new rectangle must be contained. + */ + private final Rectangle2D bounds; + + /** + * The point which is fixed during the change. In {@link #doBegin(Point2D)} it is set to {@link #getFixedCorner()}; + * in {@link #doEnd(Point2D)} it is set to {@code null}. + */ + private Point2D fixedCorner; + + // CONSTRUCTOR + + /** + * Creates a fixed corner change strategy. It respects the specified {@code ratio} if {@code ratioFixed} is + * {@code true}. + * + * @param ratioFixed + * indicates whether the ratio will be fixed + * @param ratio + * defines the fixed ratio + * @param bounds + * the bounds within which the new rectangle must be contained + */ + protected AbstractFixedPointChangeStrategy(boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio); + Objects.requireNonNull(bounds, "The argument 'bounds' must not be null."); //$NON-NLS-1$ + + this.bounds = bounds; + } + + // ABSTRACT METHODS + + /** + * Returns the corner which is fixed during the change. Called once when the change begins. + * + * @return the corner which is fixed during the change + */ + protected abstract Point2D getFixedCorner(); + + // IMPLEMENTATION OF 'do...' + + /** + * Creates a new rectangle from the two corners defined by {@link #getFixedCorner()} and the specified point. + * + * @param point + * the second corner + * @return the rectangle defined the two corners + */ + private final Rectangle2D createFromCorners(Point2D point) { + Point2D pointInBounds = Rectangles2D.inRectangle(bounds, point); + + if (isRatioFixed()) { + return Rectangles2D.forDiagonalCornersAndRatio(fixedCorner, pointInBounds, getRatio()); + } else { + return Rectangles2D.forDiagonalCorners(fixedCorner, pointInBounds); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected final Rectangle2D doBegin(Point2D point) { + boolean startPointNotInBounds = !bounds.contains(point); + if (startPointNotInBounds) { + throw new IllegalArgumentException( + "The change's start point (" + point + ") must lie within the bounds (" + bounds + ")."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + fixedCorner = getFixedCorner(); + return createFromCorners(point); + } + + /** + * {@inheritDoc} + */ + @Override + protected Rectangle2D doContinue(Point2D point) { + return createFromCorners(point); + } + + /** + * {@inheritDoc} + */ + @Override + protected final Rectangle2D doEnd(Point2D point) { + Rectangle2D newRectangle = createFromCorners(point); + fixedCorner = null; + return newRectangle; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/AbstractPreviousRectangleChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/AbstractPreviousRectangleChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..bf169d339c9fbc74589775dd4a56ae6b52a0ec20 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/AbstractPreviousRectangleChangeStrategy.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import java.util.Objects; + +import javafx.geometry.Rectangle2D; + +/** + * Abstract superclass to those implementations of {@link Rectangle2DChangeStrategy}, which are based on a previous + * rectangle. Respects a ratio if one is set. + */ +abstract class AbstractPreviousRectangleChangeStrategy extends AbstractRatioRespectingChangeStrategy { + + // ATTRIBUTES + + /** + * The rectangle these changes are based on. + */ + private final Rectangle2D previous; + + // CONSTRUCTOR + + /** + * Creates a change strategy which is based on the specified {@code previous} rectangle and respects the specified + * {@code ratio} if {@code ratioFixed} is {@code true}. + * + * @param previous + * the previous rectangle this change is based on + * @param ratioFixed + * indicates whether the ratio will be fixed + * @param ratio + * defines the fixed ratio + */ + protected AbstractPreviousRectangleChangeStrategy(Rectangle2D previous, boolean ratioFixed, double ratio) { + super(ratioFixed, ratio); + + Objects.requireNonNull(previous, "The previous rectangle must not be null."); //$NON-NLS-1$ + this.previous = previous; + } + + // ATTRIBUTE ACCESS + + /** + * The previous rectangle this change is based on. + * + * @return the previous rectangle + */ + protected final Rectangle2D getPrevious() { + return previous; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/AbstractRatioRespectingChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/AbstractRatioRespectingChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..1d89c73e73074bafe54de2aeb064b0222238fe82 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/AbstractRatioRespectingChangeStrategy.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +/** + * Abstract superclass to implementations of {@link Rectangle2DChangeStrategy}, which might be parameterized such that only + * rectangles of a defined ratio are created. This parameterization happens during construction. Subclasses must + * implement the ratio handling themselves! This class only holds the parameters. + */ +abstract class AbstractRatioRespectingChangeStrategy extends AbstractBeginEndCheckingChangeStrategy { + + // ATTRIBUTES + + /** + * Indicates whether the current selection must have a fixed ratio. If so, 'ratio' can be used. + */ + private final boolean ratioFixed; + + /** + * The currently used ratio. Should only be used if 'ratioFixed' is true. + */ + private final double ratio; + + // CONSTRUCTOR + + /** + * Creates a change strategy which respects the specified {@code ratio} if {@code ratioFixed} is {@code true}. + * + * @param ratioFixed + * indicates whether the ratio will be fixed + * @param ratio + * defines the fixed ratio + */ + protected AbstractRatioRespectingChangeStrategy(boolean ratioFixed, double ratio) { + super(); + this.ratioFixed = ratioFixed; + this.ratio = ratio; + } + + // Attribute Access + + /** + * Indicates whether the ratio is fixed. If so, the ratio can be accessed with {@link #getRatio()}. + * + * @return true if the ratio is fixed; false otherwise + */ + protected final boolean isRatioFixed() { + return ratioFixed; + } + + /** + * The current ratio. Can only be called without exception when {@link #isRatioFixed()} returns true. + * + * @return the current ratio + */ + protected final double getRatio() { + if (!ratioFixed) + throw new IllegalStateException("The ratio is not fixed."); //$NON-NLS-1$ + return ratio; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/MoveChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/MoveChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..26f8c9dc5c156c4d5841b830d2c12c5495507eec --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/MoveChangeStrategy.java @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.MathTools; + +import java.util.Objects; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * Moves the rectangle around. + */ +public class MoveChangeStrategy extends AbstractPreviousRectangleChangeStrategy { + + /* + * The previous rectangle will be moved around using a vector computed from the start to the current point. + * The moved rectangle will be forced within defined bounds. + */ + + // ATTRIBUTES + + /** + * A rectangle which defines the bounds within which the previous rectangle can be moved. + */ + private final Rectangle2D bounds; + + /** + * The starting point of the selection change. The move will be computed relative to this point. + */ + private Point2D startingPoint; + + // CONSTRUCTORS + + /** + * Creates a new change strategy which moves the specified rectangle within the specified bounds. + * + * @param previous + * the previous rectangle this move is based on + * @param bounds + * the bounds within which the rectangle can be moved + */ + public MoveChangeStrategy(Rectangle2D previous, Rectangle2D bounds) { + super(previous, false, 0); + Objects.requireNonNull(bounds, "The specified bounds must not be null."); //$NON-NLS-1$ + this.bounds = bounds; + } + + /** + * Creates a new change strategy which moves the specified rectangle within the specified bounds defined by the + * rectangle from {@code (0, 0)} to {@code (maxX, maxY)}. + * + * @param previous + * the previous rectangle this move is based on + * @param maxX + * the maximal x-coordinate of the right edge of the created rectangles; must be greater than or equal to + * the previous rectangle's width + * @param maxY + * the maximal y-coordinate of the lower edge of the created rectangles; must be greater than or equal to + * the previous rectangle's height + */ + public MoveChangeStrategy(Rectangle2D previous, double maxX, double maxY) { + super(previous, false, 0); + if (maxX < previous.getWidth()) { + throw new IllegalArgumentException( + "The specified maximal x-coordinate must be greater than or equal to the previous rectangle's width."); //$NON-NLS-1$ + } + if (maxY < previous.getHeight()) { + throw new IllegalArgumentException( + "The specified maximal y-coordinate must be greater than or equal to the previous rectangle's height."); //$NON-NLS-1$ + } + + bounds = new Rectangle2D(0, 0, maxX, maxY); + } + + // IMPLEMENTATION OF 'do...' + + /** + * Moves the previous rectangle to the specified point relative to the {@link #startingPoint}. + * + * @param point + * the vector from the {@link #startingPoint} to this point defines the movement + * @return the moved rectangle + */ + private final Rectangle2D moveRectangleToPoint(Point2D point) { + + /* + * The computation makes sure that no part of the rectangle can be moved out the bounds. + * To achieve this, the coordinates of the future rectangle's upper left corner are forced into the intervals + * - [boundsMinX, boundsMaxX - previousRectangleWidth], + * - [boundsMinY, boundsMaxY - previousRectangleHeight] respectively. + */ + + // vector from starting to specified point + double xMove = point.getX() - startingPoint.getX(); + double yMove = point.getY() - startingPoint.getY(); + + // upper left corner + double upperLeftX = getPrevious().getMinX() + xMove; + double upperLeftY = getPrevious().getMinY() + yMove; + + // upper bounds for upper left corner + double maxX = bounds.getMaxX() - getPrevious().getWidth(); + double maxY = bounds.getMaxY() - getPrevious().getHeight(); + + // corrected upper left corner + double correctedUpperLeftX = MathTools.inInterval(bounds.getMinX(), upperLeftX, maxX); + double correctedUpperLeftY = MathTools.inInterval(bounds.getMinY(), upperLeftY, maxY); + + // rectangle from corrected upper left corner with the previous rectangle's width and height + return new Rectangle2D( + correctedUpperLeftX, correctedUpperLeftY, + getPrevious().getWidth(), getPrevious().getHeight()); + } + + /** + * {@inheritDoc} + */ + @Override + protected Rectangle2D doBegin(Point2D point) { + this.startingPoint = point; + return getPrevious(); + } + + /** + * {@inheritDoc} + */ + @Override + protected Rectangle2D doContinue(Point2D point) { + return moveRectangleToPoint(point); + } + + /** + * {@inheritDoc} + */ + @Override + protected Rectangle2D doEnd(Point2D point) { + return moveRectangleToPoint(point); + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/NewChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/NewChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..e62f4b4ed74b7dab375f38fe131fa7cbb0dac2e7 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/NewChangeStrategy.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which creates a new rectangle. + */ +public class NewChangeStrategy extends AbstractFixedPointChangeStrategy { + + /* + * The new selection will have the starting point as a fixed corner. The other corner will always be the current + * point modulo the ratio which will be respected if enforced. Both is handled by the superclass. + */ + + // ATTRIBUTES + + /** + * The starting point of this change. + */ + private Point2D startingPoint; + + // CONSTRUCTOR + + /** + * Creates a change strategy which creates a new rectangle. It respects the specified {@code ratio} if + * {@code ratioFixed} is {@code true}. + * + * @param ratioFixed + * indicates whether the ratio will be fixed + * @param ratio + * defines the fixed ratio + * @param bounds + * the bounds within which the new rectangle must be contained + */ + public NewChangeStrategy(boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + } + + /** + * {@inheritDoc} + */ + @Override + protected void beforeBeginHook(Point2D point) { + startingPoint = point; + } + + /** + * {@inheritDoc} + */ + @Override + protected Point2D getFixedCorner() { + return startingPoint; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/Rectangle2DChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/Rectangle2DChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..378410670ade149778b9fb8c709c2fdccc780eb1 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/Rectangle2DChangeStrategy.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A {@code Rectangle2DChangeStrategy} creates instances of {@link Rectangle2D} based on the coordinates of the begin, + * continuation and end of an action. The behavior is undefined if these three methods are not called on the following + * order:<br> + * ({@link #beginChange(Point2D) begin} -> {@link #continueChange(Point2D) continue}* -> {@link #endChange(Point2D) end} + * )* <br> + * <br> + * Most implementations will be creating new rectangles based on an existing one. If the created ones constantly replace + * the original, this effectively "changes" the rectangle's appearance (note that {@link Rectangle2D} instances + * themselves are immutable !). This interface and its implementations were created to easily allow a GUI user to change + * an existing rectangle by typical resize and move operations. + */ +public interface Rectangle2DChangeStrategy { + + /** + * Begins the change at the specified point. + * + * @param point + * a point + * @return the new rectangle + */ + Rectangle2D beginChange(Point2D point); + + /** + * Continues the change to the specified point. Must not be called before a call to {@link #beginChange}. + * + * @param point + * a point + * @return the new rectangle + */ + Rectangle2D continueChange(Point2D point); + + /** + * Ends the change at the specified point. Must not be called before a call to {@link #beginChange}. + * + * @param point + * a point + * @return the new rectangle + */ + Rectangle2D endChange(Point2D point); + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToEastChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToEastChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..ccbe662ee1447c7a4a1b0167a98710fb5d1c935c --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToEastChangeStrategy.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.rectangle.Edge2D; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the east. + */ +public class ToEastChangeStrategy extends AbstractFixedEdgeChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's western edge as a fixed edge. The parallel edge will + * be defined by the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's western edge. + */ + private final Edge2D westernEdge; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the east. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the rectangle can be resized + */ + public ToEastChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + Point2D edgeCenterPoint = new Point2D(original.getMinX(), (original.getMinY() + original.getMaxY()) / 2); + westernEdge = new Edge2D(edgeCenterPoint, Orientation.VERTICAL, original.getMaxY() - original.getMinY()); + } + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the northeast. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param maxX + * the maximal x-coordinate of the right edge of the created rectangles; must be greater than or equal to + * the previous rectangle's width + * @param maxY + * the maximal y-coordinate of the lower edge of the created rectangles; must be greater than or equal to + * the previous rectangle's height + */ + public ToEastChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, double maxX, double maxY) { + this(original, ratioFixed, ratio, new Rectangle2D(0, 0, maxX, maxY)); + } + + // IMPLEMENTATION OF 'AbstractFixedEdgeChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Edge2D getFixedEdge() { + return westernEdge; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToNorthChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToNorthChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..29674d0f4254edcd96599c383e0125c9875d2caf --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToNorthChangeStrategy.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.rectangle.Edge2D; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the north. + */ +public class ToNorthChangeStrategy extends AbstractFixedEdgeChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's southern edge as a fixed edge. The parallel edge will + * be defined by the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's southern edge. + */ + private final Edge2D southernEdge; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the north. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the rectangle can be resized + */ + public ToNorthChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + Point2D edgeCenterPoint = new Point2D((original.getMinX() + original.getMaxX()) / 2, original.getMaxY()); + southernEdge = new Edge2D(edgeCenterPoint, Orientation.HORIZONTAL, original.getMaxX() - original.getMinX()); + } + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the northeast. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param maxX + * the maximal x-coordinate of the right edge of the created rectangles; must be greater than or equal to + * the previous rectangle's width + * @param maxY + * the maximal y-coordinate of the lower edge of the created rectangles; must be greater than or equal to + * the previous rectangle's height + */ + public ToNorthChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, double maxX, double maxY) { + this(original, ratioFixed, ratio, new Rectangle2D(0, 0, maxX, maxY)); + } + + // IMPLEMENTATION OF 'AbstractFixedEdgeChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Edge2D getFixedEdge() { + return southernEdge; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToNortheastChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToNortheastChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..5edb288151ac4f27a3538fecaa73fc602da8ef0a --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToNortheastChangeStrategy.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the northeast. + */ +public class ToNortheastChangeStrategy extends AbstractFixedPointChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's southwestern corner as a fixed corner. The other corner will + * always be the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's southwestern corner. + */ + private final Point2D southwesternCorner; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the northeast. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the new rectangle must be contained + */ + public ToNortheastChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + southwesternCorner = new Point2D(original.getMinX(), original.getMaxY()); + } + + // IMPLEMENTATION OF 'AbstractFixedPointChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Point2D getFixedCorner() { + return southwesternCorner; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToNorthwestChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToNorthwestChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..183133367ee4289d13ffc61e6435ea82d9bf664e --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToNorthwestChangeStrategy.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the northwest. + */ +public class ToNorthwestChangeStrategy extends AbstractFixedPointChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's southeastern corner as a fixed corner. The other corner will + * always be the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's southeastern corner. + */ + private final Point2D southeasternCorner; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the northwest. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the new rectangle must be contained + */ + public ToNorthwestChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + southeasternCorner = new Point2D(original.getMaxX(), original.getMaxY()); + } + + // IMPLEMENTATION OF 'AbstractFixedPointChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Point2D getFixedCorner() { + return southeasternCorner; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToSouthChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToSouthChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..3ea9f78ea185543f2d340c2fa17016cf0190bc94 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToSouthChangeStrategy.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.rectangle.Edge2D; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the south. + */ +public class ToSouthChangeStrategy extends AbstractFixedEdgeChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's northern edge as a fixed edge. The parallel edge will + * be defined by the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's northern edge. + */ + private final Edge2D northernEdge; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the south. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the rectangle can be resized + */ + public ToSouthChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + Point2D edgeCenterPoint = new Point2D((original.getMinX() + original.getMaxX()) / 2, original.getMinY()); + northernEdge = new Edge2D(edgeCenterPoint, Orientation.HORIZONTAL, original.getMaxX() - original.getMinX()); + } + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the northeast. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param maxX + * the maximal x-coordinate of the right edge of the created rectangles; must be greater than or equal to + * the previous rectangle's width + * @param maxY + * the maximal y-coordinate of the lower edge of the created rectangles; must be greater than or equal to + * the previous rectangle's height + */ + public ToSouthChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, double maxX, double maxY) { + this(original, ratioFixed, ratio, new Rectangle2D(0, 0, maxX, maxY)); + } + + // IMPLEMENTATION OF 'AbstractFixedEdgeChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Edge2D getFixedEdge() { + return northernEdge; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToSoutheastChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToSoutheastChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..430c3f5b2db94b8bc61b974d34c4df84dd035f60 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToSoutheastChangeStrategy.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the southeast. + */ +public class ToSoutheastChangeStrategy extends AbstractFixedPointChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's northwestern corner as a fixed corner. The other corner will + * always be the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's northwestern corner. + */ + private final Point2D northwesternCorner; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the southeast. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the new rectangle must be contained + */ + public ToSoutheastChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + northwesternCorner = new Point2D(original.getMinX(), original.getMinY()); + } + + // IMPLEMENTATION OF 'AbstractFixedPointChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Point2D getFixedCorner() { + return northwesternCorner; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToSouthwestChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToSouthwestChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..cad2aeee18f553e2360304012d594d5f5c900e91 --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToSouthwestChangeStrategy.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the southwest. + * + * @author pan + */ +public class ToSouthwestChangeStrategy extends AbstractFixedPointChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's northeastern corner as a fixed corner. The other corner will + * always be the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's northeastern corner. + */ + private final Point2D northeasternCorner; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the southwest. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the new rectangle must be contained + */ + public ToSouthwestChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + northeasternCorner = new Point2D(original.getMaxX(), original.getMinY()); + } + + // IMPLEMENTATION OF 'AbstractFixedPointChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Point2D getFixedCorner() { + return northeasternCorner; + } + +} diff --git a/src/impl/org/controlsfx/tools/rectangle/change/ToWestChangeStrategy.java b/src/impl/org/controlsfx/tools/rectangle/change/ToWestChangeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..4500992c6db9dc3a3e8c4ba5da3d54c9b8d29d7e --- /dev/null +++ b/src/impl/org/controlsfx/tools/rectangle/change/ToWestChangeStrategy.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.tools.rectangle.change; + +import impl.org.controlsfx.tools.rectangle.Edge2D; +import javafx.geometry.Orientation; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; + +/** + * A strategy which enlarges an existing rectangle to the west. + */ +public class ToWestChangeStrategy extends AbstractFixedEdgeChangeStrategy { + + /* + * The new rectangle will have the existing rectangle's eastern edge as a fixed edge. The parallel edge will + * be defined by the current point (modulo the ratio which will be respected if enforced), which is handled by the + * superclass. + */ + + // ATTRIBUTES + + /** + * The new rectangle's eastern edge. + */ + private final Edge2D easternEdge; + + // CONSTRUCTOR + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the west. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param bounds + * the bounds within which the rectangle can be resized + */ + public ToWestChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, Rectangle2D bounds) { + super(ratioFixed, ratio, bounds); + Point2D edgeCenterPoint = new Point2D(original.getMaxX(), (original.getMinY() + original.getMaxY()) / 2); + easternEdge = new Edge2D(edgeCenterPoint, Orientation.VERTICAL, original.getMaxY() - original.getMinY()); + } + + /** + * Creates a new change strategy which enlarges the specified {@code original} rectangle to the northeast. The given + * {@code ratio} is enforced when indicated by {@code ratioFixed}. + * + * @param original + * the original rectangle + * @param ratioFixed + * indicates whether the rectangle's ratio will be fixed to the {@code ratio} + * @param ratio + * the possibly fixed ratio of the rectangle created by this strategy + * @param maxX + * the maximal x-coordinate of the right edge of the created rectangles; must be greater than or equal to + * the previous rectangle's width + * @param maxY + * the maximal y-coordinate of the lower edge of the created rectangles; must be greater than or equal to + * the previous rectangle's height + */ + public ToWestChangeStrategy(Rectangle2D original, boolean ratioFixed, double ratio, double maxX, double maxY) { + this(original, ratioFixed, ratio, new Rectangle2D(0, 0, maxX, maxY)); + } + + // IMPLEMENTATION OF 'AbstractFixedEdgeChangeStrategy' + + /** + * {@inheritDoc} + */ + @Override + protected Edge2D getFixedEdge() { + return easternEdge; + } + +} diff --git a/src/impl/org/controlsfx/version/VersionChecker.java b/src/impl/org/controlsfx/version/VersionChecker.java new file mode 100644 index 0000000000000000000000000000000000000000..4e91d0f8d8dd8af36187e48d7fa893abccc6b2de --- /dev/null +++ b/src/impl/org/controlsfx/version/VersionChecker.java @@ -0,0 +1,222 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package impl.org.controlsfx.version; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +import com.sun.javafx.runtime.VersionInfo; + +public class VersionChecker { + + private static final String javaFXVersion; + private static final String controlsFXSpecTitle; + private static final String controlsFXSpecVersion; + private static final String controlsFXImpVersion; + + private static final Package controlsFX; + + private static Properties props; + + static { + controlsFX = VersionChecker.class.getPackage(); + + javaFXVersion = VersionInfo.getVersion(); + controlsFXSpecTitle = getControlsFXSpecificationTitle(); + controlsFXSpecVersion = getControlsFXSpecificationVersion(); + controlsFXImpVersion = getControlsFXImplementationVersion(); + } + + private VersionChecker() { + // no-op + } + + public static void doVersionCheck() { + // We keep ControlsFX bleeding edge, and we try to let our version numbers + // do the talking. However, we can't always ensure people do the right + // thing, so here we will check the ControlsFX and JavaFX version numbers, + // to ensure they match. + // Fortunately, our system is simple at present: we use the + // 'controlsFXSpec' value to represent what we require. In other + // words, ControlsFX 8.0.0 has controlsFXSpecVersion of 8.0.0, so it will work on + // JavaFX 8.0.0 and later versions. Conversely, ControlsFX 8.0.6_20 has a controlsFXSpecVersion of + // 8.0.20 (controlsFXSpecTitle of Java 8u20), which means that ControlsFX will only work on JavaFX 8u20 + // and later versions. + + if (controlsFXSpecVersion == null) { + // FIXME temporary fix to allow ControlsFX to work when run inside + // an IDE (i.e. for developers of ControlsFX). + return; + } + + Comparable[] splitSpecVersion = toComparable(controlsFXSpecVersion.split("\\.")); //$NON-NLS-1$ + + // javaFXVersion may contain '-' like 8.0.20-ea so replace them with '.' before splitting. + Comparable[] splitJavaVersion = toComparable(javaFXVersion.replace('-', '.').split("\\.")); //$NON-NLS-1$ + + boolean notSupportedVersion = false; + + // Check Major Version + if (splitSpecVersion[0].compareTo(splitJavaVersion[0]) > 0) { + notSupportedVersion = true; + } else if (splitSpecVersion[0].compareTo(splitJavaVersion[0]) == 0) { + // Check Minor Version + if (splitSpecVersion[1].compareTo(splitJavaVersion[2])>0) { + notSupportedVersion = true; + } + } + + if (notSupportedVersion) { + throw new RuntimeException("ControlsFX Error: ControlsFX " + //$NON-NLS-1$ + controlsFXImpVersion + " requires at least " + controlsFXSpecTitle); //$NON-NLS-1$ + } + } + + private static Comparable<Comparable>[] toComparable(String[] tokens) { + Comparable[] ret= new Comparable[tokens.length]; + for (int i = 0; i<tokens.length; i++) { + String token = tokens[i]; + try { + ret[i] = new Integer(token); + } + catch (NumberFormatException e) { + ret[i] = token; + } + } + return ret; + } + + private static String getControlsFXSpecificationTitle() { + // firstly try to read from manifest + try { + return controlsFX.getSpecificationTitle(); + } catch (NullPointerException e) { + // no-op + } + + // try to read it from the controlsfx-build.properties if running + // from within an IDE + return getPropertyValue("controlsfx_specification_title"); //$NON-NLS-1$ + + +// try { +// Properties prop = new Properties(); +// File file = new File("../controlsfx-build.properties"); +// if (file.exists()) { +// prop.load(new FileReader(file)); +// String version = prop.getProperty("controlsfx_specification_title"); +// if (version != null && !version.isEmpty()) { +// return version; +// } +// } +// } catch (IOException e) { +// // no-op +// } +// +// return null; + } + + private static String getControlsFXSpecificationVersion() { + + // firstly try to read from manifest + try { + return controlsFX.getSpecificationVersion(); + } catch (NullPointerException e) { + // no-op + } + + // try to read it from the controlsfx-build.properties if running + // from within an IDE + return getPropertyValue("controlsfx_specification_title"); //$NON-NLS-1$ + +// try { +// Properties prop = new Properties(); +// File file = new File("../controlsfx-build.properties"); +// if (file.exists()) { +// prop.load(new FileReader(file)); +// String version = prop.getProperty("controlsfx_specification_version"); +// if (version != null && !version.isEmpty()) { +// return version; +// } +// } +// } catch (IOException e) { +// // no-op +// } +// +// return null; + } + + private static String getControlsFXImplementationVersion() { + + // firstly try to read from manifest + try { + return controlsFX.getImplementationVersion(); + } catch (NullPointerException e) { + // no-op + } + + // try to read it from the controlsfx-build.properties if running + // from within an IDE + + return getPropertyValue("controlsfx_specification_title") + //$NON-NLS-1$ + getPropertyValue("artifact_suffix"); //$NON-NLS-1$ + + +// try { +// Properties prop = new Properties(); +// File file = new File("../controlsfx-build.properties"); +// if (file.exists()) { +// prop.load(new FileReader(file)); +// String version = prop.getProperty("controlsfx_version"); +// if (version != null && !version.isEmpty()) { +// return version; +// } +// } +// } catch (IOException e) { +// // no-op +// } +// +// return null; + } + + private static synchronized String getPropertyValue(String key) { + + if ( props == null ) { + try { + File file = new File("../controlsfx-build.properties"); //$NON-NLS-1$ + if (file.exists()) { + props.load(new FileReader(file)); + } + } catch (IOException e) { + // no-op + } + } + return props.getProperty(key); + } +} diff --git a/src/org/controlsfx/control/BreadCrumbBar.java b/src/org/controlsfx/control/BreadCrumbBar.java new file mode 100644 index 0000000000000000000000000000000000000000..38bfe10c8cba2c15aae02a85107f354ac4c7bc27 --- /dev/null +++ b/src/org/controlsfx/control/BreadCrumbBar.java @@ -0,0 +1,343 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.BreadCrumbBarSkin; +import impl.org.controlsfx.skin.BreadCrumbBarSkin.BreadCrumbButton; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.scene.control.Button; +import javafx.scene.control.Skin; +import javafx.scene.control.TreeItem; +import javafx.util.Callback; + +import com.sun.javafx.event.EventHandlerManager; + +/** + * Represents a bread crumb bar. This control is useful to visualize and navigate + * a hierarchical path structure, such as file systems. + * + * <p>Shown below is a screenshot of the BreadCrumbBar control: + * + * <br> + * <center> + * <img src="breadCrumbBar.png" alt="Screenshot of BreadCrumbBar"> + * </center> + */ +public class BreadCrumbBar<T> extends ControlsFXControl { + + private final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); + + + /** + * Represents an Event which is fired when a bread crumb was activated. + */ + @SuppressWarnings("serial") + public static class BreadCrumbActionEvent<TE> extends Event { + + /** + * The event type that should be listened to by people interested in + * knowing when the {@link BreadCrumbBar#selectedCrumbProperty() selected crumb} + * has changed. + */ + @SuppressWarnings("rawtypes") + public static final EventType<BreadCrumbActionEvent> CRUMB_ACTION = new EventType<>("CRUMB_ACTION"); //$NON-NLS-1$ + + private final TreeItem<TE> selectedCrumb; + + /** + * Creates a new event that can subsequently be fired. + */ + public BreadCrumbActionEvent(TreeItem<TE> selectedCrumb) { + super(CRUMB_ACTION); + this.selectedCrumb = selectedCrumb; + } + + /** + * Returns the crumb which was the action target. + */ + public TreeItem<TE> getSelectedCrumb() { + return selectedCrumb; + } + } + + + + /** + * Construct a tree model from the flat list which then can be set + * as selectedCrumb node to be shown + * @param crumbs + */ + public static <T> TreeItem<T> buildTreeModel(@SuppressWarnings("unchecked") T... crumbs){ + TreeItem<T> subRoot = null; + for (T crumb : crumbs) { + TreeItem<T> currentNode = new TreeItem<>(crumb); + if(subRoot == null){ + subRoot = currentNode; + }else{ + subRoot.getChildren().add(currentNode); + subRoot = currentNode; + } + } + return subRoot; + } + + + + + + + /*************************************************************************** + * + * Private fields + * + **************************************************************************/ + + + /** + * Default crumb node factory. This factory is used when no custom factory is specified by the user. + */ + private final Callback<TreeItem<T>, Button> defaultCrumbNodeFactory = new Callback<TreeItem<T>, Button>(){ + @Override + public Button call(TreeItem<T> crumb) { + return new BreadCrumbBarSkin.BreadCrumbButton(crumb.getValue() != null ? crumb.getValue().toString() : ""); //$NON-NLS-1$ + } + }; + + + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Creates an empty bread crumb bar + */ + public BreadCrumbBar(){ + this(null); + } + + /** + * Creates a bread crumb bar with the given TreeItem as the currently + * selected crumb. + */ + public BreadCrumbBar(TreeItem<T> selectedCrumb) { + getStyleClass().add(DEFAULT_STYLE_CLASS); + setSelectedCrumb(selectedCrumb); + setCrumbFactory(defaultCrumbNodeFactory); + } + + + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + return tail.prepend(eventHandlerManager); + } + + + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + // --- selectedCrumb + /** + * Represents the bottom-most path node (the node on the most-right side in + * terms of the bread crumb bar). The full path is then being constructed + * using getParent() of the tree-items. + * + * <p> + * Consider the following hierarchy: + * [Root] > [Folder] > [SubFolder] > [myfile.txt] + * + * To show the above bread crumb bar, you have to set the [myfile.txt] tree-node as selected crumb. + */ + public final ObjectProperty<TreeItem<T>> selectedCrumbProperty() { + return selectedCrumb; + } + private final ObjectProperty<TreeItem<T>> selectedCrumb = + new SimpleObjectProperty<>(this, "selectedCrumb"); //$NON-NLS-1$ + + /** + * Get the current target path + */ + public final TreeItem<T> getSelectedCrumb() { + return selectedCrumb.get(); + } + + /** + * Select one node in the BreadCrumbBar for being the bottom-most path node. + * @param selectedCrumb + */ + public final void setSelectedCrumb(TreeItem<T> selectedCrumb){ + this.selectedCrumb.set(selectedCrumb); + } + + + // --- autoNavigation + /** + * Enable or disable auto navigation (default is enabled). + * If auto navigation is enabled, it will automatically navigate to the crumb which was clicked by the user. + * @return a {@link BooleanProperty} + */ + public final BooleanProperty autoNavigationEnabledProperty() { + return autoNavigation; + } + + private final BooleanProperty autoNavigation = + new SimpleBooleanProperty(this, "autoNavigationEnabled", true); //$NON-NLS-1$ + + /** + * Return whether auto-navigation is enabled. + * @return whether auto-navigation is enabled. + */ + public final boolean isAutoNavigationEnabled() { + return autoNavigation.get(); + } + + /** + * Enable or disable auto navigation (default is enabled). + * If auto navigation is enabled, it will automatically navigate to the crumb which was clicked by the user. + * @param enabled + */ + public final void setAutoNavigationEnabled(boolean enabled) { + autoNavigation.set(enabled); + } + + + + // --- crumbFactory + /** + * Return an ObjectProperty of the CrumbFactory. + * @return an ObjectProperty of the CrumbFactory. + */ + public final ObjectProperty<Callback<TreeItem<T>, Button>> crumbFactoryProperty() { + return crumbFactory; + } + + private final ObjectProperty<Callback<TreeItem<T>, Button>> crumbFactory = + new SimpleObjectProperty<>(this, "crumbFactory"); //$NON-NLS-1$ + + /** + * Sets the crumb factory to create (custom) {@link BreadCrumbButton} instances. + * <code>null</code> is not allowed and will result in a fall back to the default factory. + * @param value + */ + public final void setCrumbFactory(Callback<TreeItem<T>, Button> value) { + if(value == null){ + value = defaultCrumbNodeFactory; + } + crumbFactoryProperty().set(value); + } + + /** + * Returns the cell factory that will be used to create {@link BreadCrumbButton} + * instances + */ + public final Callback<TreeItem<T>, Button> getCrumbFactory() { + return crumbFactory.get(); + } + + + // --- onCrumbAction + /** + * @return an ObjectProperty representing the crumbAction EventHandler being used. + */ + public final ObjectProperty<EventHandler<BreadCrumbActionEvent<T>>> onCrumbActionProperty() { + return onCrumbAction; + } + + /** + * Set a new EventHandler for when a user selects a crumb. + * @param value + */ + public final void setOnCrumbAction(EventHandler<BreadCrumbActionEvent<T>> value) { + onCrumbActionProperty().set(value); + } + + /** + * Return the EventHandler currently used when a user selects a crumb. + * @return the EventHandler currently used when a user selects a crumb. + */ + public final EventHandler<BreadCrumbActionEvent<T>> getOnCrumbAction() { + return onCrumbActionProperty().get(); + } + + private ObjectProperty<EventHandler<BreadCrumbActionEvent<T>>> onCrumbAction = new ObjectPropertyBase<EventHandler<BreadCrumbBar.BreadCrumbActionEvent<T>>>() { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override protected void invalidated() { + eventHandlerManager.setEventHandler(BreadCrumbActionEvent.CRUMB_ACTION, (EventHandler<BreadCrumbActionEvent>)(Object)get()); + } + + @Override + public Object getBean() { + return BreadCrumbBar.this; + } + + @Override + public String getName() { + return "onCrumbAction"; //$NON-NLS-1$ + } + }; + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "bread-crumb-bar"; //$NON-NLS-1$ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new BreadCrumbBarSkin<>(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(BreadCrumbBar.class, "breadcrumbbar.css"); + } +} diff --git a/src/org/controlsfx/control/CheckBitSetModelBase.java b/src/org/controlsfx/control/CheckBitSetModelBase.java new file mode 100644 index 0000000000000000000000000000000000000000..0ba976f2f469d10e6b27ee35c71ac179a208bcd8 --- /dev/null +++ b/src/org/controlsfx/control/CheckBitSetModelBase.java @@ -0,0 +1,315 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import java.util.BitSet; +import java.util.Map; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +import com.sun.javafx.collections.MappingChange; +import com.sun.javafx.collections.NonIterableChange; +import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; + +// not public API +abstract class CheckBitSetModelBase<T> implements IndexedCheckModel<T> { + + /*********************************************************************** + * * + * Internal properties * + * * + **********************************************************************/ + + private final Map<T, BooleanProperty> itemBooleanMap; + + private final BitSet checkedIndices; + private final ReadOnlyUnbackedObservableList<Integer> checkedIndicesList; + private final ReadOnlyUnbackedObservableList<T> checkedItemsList; + + + + /*********************************************************************** + * * + * Constructors * + * * + **********************************************************************/ + + CheckBitSetModelBase(final Map<T, BooleanProperty> itemBooleanMap) { + this.itemBooleanMap = itemBooleanMap; + + this.checkedIndices = new BitSet(); + + this.checkedIndicesList = new ReadOnlyUnbackedObservableList<Integer>() { + @Override public Integer get(int index) { + if (index < 0 || index >= getItemCount()) return -1; + + for (int pos = 0, val = checkedIndices.nextSetBit(0); + val >= 0 || pos == index; + pos++, val = checkedIndices.nextSetBit(val+1)) { + if (pos == index) return val; + } + + return -1; + } + + @Override public int size() { + return checkedIndices.cardinality(); + } + + @Override public boolean contains(Object o) { + if (o instanceof Number) { + Number n = (Number) o; + int index = n.intValue(); + + return index >= 0 && index < checkedIndices.length() && + checkedIndices.get(index); + } + + return false; + } + }; + + this.checkedItemsList = new ReadOnlyUnbackedObservableList<T>() { + @Override public T get(int i) { + int pos = checkedIndicesList.get(i); + if (pos < 0 || pos >= getItemCount()) return null; + return getItem(pos); + } + + @Override public int size() { + return checkedIndices.cardinality(); + } + }; + + final MappingChange.Map<Integer,T> map = f -> getItem(f); + + checkedIndicesList.addListener(new ListChangeListener<Integer>() { + @Override public void onChanged(final Change<? extends Integer> c) { + // when the selectedIndices ObservableList changes, we manually call + // the observers of the selectedItems ObservableList. + boolean hasRealChangeOccurred = false; + while (c.next() && ! hasRealChangeOccurred) { + hasRealChangeOccurred = c.wasAdded() || c.wasRemoved(); + } + + if (hasRealChangeOccurred) { + c.reset(); + checkedItemsList.callObservers(new MappingChange<>(c, map, checkedItemsList)); + } + c.reset(); + } + }); + + // this code is to handle the situation where a developer is manually + // toggling the check model, and expecting the UI to update (without + // this it won't happen!). + getCheckedItems().addListener(new ListChangeListener<T>() { + @Override public void onChanged(ListChangeListener.Change<? extends T> c) { + while (c.next()) { + if (c.wasAdded()) { + for (T item : c.getAddedSubList()) { + BooleanProperty p = getItemBooleanProperty(item); + if (p != null) { + p.set(true); + } + } + } + + if (c.wasRemoved()) { + for (T item : c.getRemoved()) { + BooleanProperty p = getItemBooleanProperty(item); + if (p != null) { + p.set(false); + } + } + } + } + } + }); + } + + + + /*********************************************************************** + * * + * Abstract API * + * * + **********************************************************************/ + + @Override + public abstract T getItem(int index); + + @Override + public abstract int getItemCount(); + + @Override + public abstract int getItemIndex(T item); + + BooleanProperty getItemBooleanProperty(T item) { + return itemBooleanMap.get(item); + } + + + /*********************************************************************** + * * + * Public selection API * + * * + **********************************************************************/ + + /** + * Returns a read-only list of the currently checked indices in the CheckBox. + */ + @Override + public ObservableList<Integer> getCheckedIndices() { + return checkedIndicesList; + } + + /** + * Returns a read-only list of the currently checked items in the CheckBox. + */ + @Override + public ObservableList<T> getCheckedItems() { + return checkedItemsList; + } + + /** {@inheritDoc} */ + @Override + public void checkAll() { + for (int i = 0; i < getItemCount(); i++) { + check(i); + } + } + + /** {@inheritDoc} */ + @Override + public void checkIndices(int... indices) { + for (int i = 0; i < indices.length; i++) { + check(indices[i]); + } + } + + /** {@inheritDoc} */ + @Override public void clearCheck(T item) { + int index = getItemIndex(item); + clearCheck(index); + } + + /** {@inheritDoc} */ + @Override + public void clearChecks() { + for( int index = 0; index < checkedIndices.length(); index++) { + clearCheck(index); + } + } + + /** {@inheritDoc} */ + @Override + public void clearCheck(int index) { + if (index < 0 || index >= getItemCount()) return; + checkedIndices.clear(index); + + final int changeIndex = checkedIndicesList.indexOf(index); + checkedIndicesList.callObservers(new NonIterableChange.SimpleRemovedChange<>(changeIndex, changeIndex, index, checkedIndicesList)); + } + + /** {@inheritDoc} */ + @Override + public boolean isEmpty() { + return checkedIndices.isEmpty(); + } + + /** {@inheritDoc} */ + @Override public boolean isChecked(T item) { + int index = getItemIndex(item); + return isChecked(index); + } + + /** {@inheritDoc} */ + @Override + public boolean isChecked(int index) { + return checkedIndices.get(index); + } + + /** {@inheritDoc} */ + @Override + public void check(int index) { + if (index < 0 || index >= getItemCount()) return; + checkedIndices.set(index); + final int changeIndex = checkedIndicesList.indexOf(index); + checkedIndicesList.callObservers(new NonIterableChange.SimpleAddChange<>(changeIndex, changeIndex+1, checkedIndicesList)); + } + + /** {@inheritDoc} */ + @Override + public void check(T item) { + int index = getItemIndex(item); + check(index); + } + + + + + /*********************************************************************** + * * + * Private implementation * + * * + **********************************************************************/ + + protected void updateMap() { + // reset the map + itemBooleanMap.clear(); + for (int i = 0; i < getItemCount(); i++) { + final int index = i; + final T item = getItem(index); + + final BooleanProperty booleanProperty = new SimpleBooleanProperty(item, "selected", false); //$NON-NLS-1$ + itemBooleanMap.put(item, booleanProperty); + + // this is where we listen to changes to the boolean properties, + // updating the selected indices list (and therefore indirectly + // the selected items list) when the checkbox is toggled + booleanProperty.addListener(new InvalidationListener() { + @Override public void invalidated(Observable o) { + if (booleanProperty.get()) { + checkedIndices.set(index); + final int changeIndex = checkedIndicesList.indexOf(index); + checkedIndicesList.callObservers(new NonIterableChange.SimpleAddChange<>(changeIndex, changeIndex+1, checkedIndicesList)); + } else { + final int changeIndex = checkedIndicesList.indexOf(index); + checkedIndices.clear(index); + checkedIndicesList.callObservers(new NonIterableChange.SimpleRemovedChange<>(changeIndex, changeIndex, index, checkedIndicesList)); + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/org/controlsfx/control/CheckComboBox.java b/src/org/controlsfx/control/CheckComboBox.java new file mode 100644 index 0000000000000000000000000000000000000000..86f175e63bc87f17a95f16ad237091054621eb29 --- /dev/null +++ b/src/org/controlsfx/control/CheckComboBox.java @@ -0,0 +1,297 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.CheckComboBoxSkin; + +import java.util.HashMap; +import java.util.Map; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Skin; +import javafx.util.StringConverter; + +/** + * A simple UI control that makes it possible to select zero or more items within + * a ComboBox-like control. Each row item shows a {@link CheckBox}, and the state + * of each row can be queried via the {@link #checkModelProperty() check model}. + * + * <h3>Screenshot</h3> + * <p>The following screenshot shows the CheckComboBox with some sample data: + * + * <br> + * <img src="checkComboBox.png" alt="Screenshot of CheckComboBox"> + * + * <h3>Code Example:</h3> + * <p>To create the CheckComboBox shown in the screenshot, simply do the + * following: + * + * <pre> + * {@code + * // create the data to show in the CheckComboBox + * final ObservableList<String> strings = FXCollections.observableArrayList(); + * for (int i = 0; i <= 100; i++) { + * strings.add("Item " + i); + * } + * + * // Create the CheckComboBox with the data + * final CheckComboBox<String> checkComboBox = new CheckComboBox<String>(strings); + * + * // and listen to the relevant events (e.g. when the selected indices or + * // selected items change). + * checkComboBox.getCheckModel().getSelectedItems().addListener(new ListChangeListener<String>() { + * public void onChanged(ListChangeListener.Change<? extends String> c) { + * System.out.println(checkComboBox.getCheckModel().getSelectedItems()); + * } + * });} + * }</pre> + * + * @param <T> The type of the data in the ComboBox. + */ +public class CheckComboBox<T> extends ControlsFXControl { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private final ObservableList<T> items; + private final Map<T, BooleanProperty> itemBooleanMap; + + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a new CheckComboBox instance with an empty list of choices. + */ + public CheckComboBox() { + this(null); + } + + /** + * Creates a new CheckComboBox instance with the given items available as + * choices. + * + * @param items The items to display within the CheckComboBox. + */ + public CheckComboBox(final ObservableList<T> items) { + final int initialSize = items == null ? 32 : items.size(); + + this.itemBooleanMap = new HashMap<>(initialSize); + this.items = items == null ? FXCollections.<T>observableArrayList() : items; + setCheckModel(new CheckComboBoxBitSetCheckModel<>(this.items, itemBooleanMap)); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new CheckComboBoxSkin<>(this); + } + + /** + * Represents the list of choices available to the user, from which they can + * select zero or more items. + */ + public ObservableList<T> getItems() { + return items; + } + + /** + * Returns the {@link BooleanProperty} for a given item index in the + * CheckComboBox. This is useful if you want to bind to the property. + */ + public BooleanProperty getItemBooleanProperty(int index) { + if (index < 0 || index >= items.size()) return null; + return getItemBooleanProperty(getItems().get(index)); + } + + /** + * Returns the {@link BooleanProperty} for a given item in the + * CheckComboBox. This is useful if you want to bind to the property. + */ + public BooleanProperty getItemBooleanProperty(T item) { + return itemBooleanMap.get(item); + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- Check Model + private ObjectProperty<IndexedCheckModel<T>> checkModel = + new SimpleObjectProperty<>(this, "checkModel"); //$NON-NLS-1$ + + /** + * Sets the 'check model' to be used in the CheckComboBox - this is the + * code that is responsible for representing the selected state of each + * {@link CheckBox} - that is, whether each {@link CheckBox} is checked or + * not (and not to be confused with the + * selection model concept, which is used in the ComboBox control to + * represent the selection state of each row).. + */ + public final void setCheckModel(IndexedCheckModel<T> value) { + checkModelProperty().set(value); + } + + /** + * Returns the currently installed check model. + */ + public final IndexedCheckModel<T> getCheckModel() { + return checkModel == null ? null : checkModel.get(); + } + + /** + * The check model provides the API through which it is possible + * to check single or multiple items within a CheckComboBox, as well as inspect + * which items have been checked by the user. Note that it has a generic + * type that must match the type of the CheckComboBox itself. + */ + public final ObjectProperty<IndexedCheckModel<T>> checkModelProperty() { + return checkModel; + } + + // --- converter + private ObjectProperty<StringConverter<T>> converter = + new SimpleObjectProperty<StringConverter<T>>(this, "converter"); + + /** + * A {@link StringConverter} that, given an object of type T, will + * return a String that can be used to represent the object visually. + */ + public final ObjectProperty<StringConverter<T>> converterProperty() { + return converter; + } + + /** + * Sets the {@link StringConverter} to be used in the control. + * @param value A {@link StringConverter} that, given an object of type T, will + * return a String that can be used to represent the object visually. + */ + public final void setConverter(StringConverter<T> value) { + converterProperty().set(value); + } + + /** + * A {@link StringConverter} that, given an object of type T, will + * return a String that can be used to represent the object visually. + */ + public final StringConverter<T> getConverter() { + return converterProperty().get(); + } + + + + /************************************************************************** + * + * Implementation + * + **************************************************************************/ + + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + private static class CheckComboBoxBitSetCheckModel<T> extends CheckBitSetModelBase<T> { + + /*********************************************************************** + * * + * Internal properties * + * * + **********************************************************************/ + + private final ObservableList<T> items; + + + + /*********************************************************************** + * * + * Constructors * + * * + **********************************************************************/ + + CheckComboBoxBitSetCheckModel(final ObservableList<T> items, final Map<T, BooleanProperty> itemBooleanMap) { + super(itemBooleanMap); + + this.items = items; + this.items.addListener(new ListChangeListener<T>() { + @Override public void onChanged(Change<? extends T> c) { + updateMap(); + } + }); + + updateMap(); + } + + + + /*********************************************************************** + * * + * Implementing abstract API * + * * + **********************************************************************/ + + @Override public T getItem(int index) { + return items.get(index); + } + + @Override public int getItemCount() { + return items.size(); + } + + @Override public int getItemIndex(T item) { + return items.indexOf(item); + } + } +} diff --git a/src/org/controlsfx/control/CheckListView.java b/src/org/controlsfx/control/CheckListView.java new file mode 100644 index 0000000000000000000000000000000000000000..1ec23b02c3e5fb816989b110d458826aef0409d5 --- /dev/null +++ b/src/org/controlsfx/control/CheckListView.java @@ -0,0 +1,264 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import java.util.HashMap; +import java.util.Map; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ListView; +import javafx.scene.control.cell.CheckBoxListCell; +import javafx.util.Callback; + +/** + * A simple UI control that makes it possible to select zero or more items within + * a ListView without the need to set a custom cell factory or manually create + * boolean properties for each row - simply use the + * {@link #checkModelProperty() check model} to request the current selection + * state. + * + * <h3>Screenshot</h3> + * <p>The following screenshot shows the CheckListView with some sample data: + * + * <br> + * <img src="checkListView.png" alt="Screenshot of CheckListView"> + * + * <h3>Code Example:</h3> + * <p>To create the CheckListView shown in the screenshot, simply do the + * following: + * + * <pre> + * {@code + * // create the data to show in the CheckListView + * final ObservableList<String> strings = FXCollections.observableArrayList(); + * for (int i = 0; i <= 100; i++) { + * strings.add("Item " + i); + * } + * + * // Create the CheckListView with the data + * final CheckListView<String> checkListView = new CheckListView<>(strings); + * + * // and listen to the relevant events (e.g. when the selected indices or + * // selected items change). + * checkListView.getCheckModel().getCheckedItems().addListener(new ListChangeListener<String>() { + * public void onChanged(ListChangeListener.Change<? extends String> c) { + * System.out.println(checkListView.getCheckModel().getCheckedItems()); + * } + * }); + * }</pre> + * + * @param <T> The type of the data in the CheckListView. + */ +public class CheckListView<T> extends ListView<T> { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private final Map<T, BooleanProperty> itemBooleanMap; + + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a new CheckListView instance with an empty list of choices. + */ + public CheckListView() { + this(FXCollections.<T> observableArrayList()); + } + + /** + * Creates a new CheckListView instance with the given items available as + * choices. + * + * @param items The items to display within the CheckListView. + */ + public CheckListView(ObservableList<T> items) { + super(items); + this.itemBooleanMap = new HashMap<>(); + + setCheckModel(new CheckListViewBitSetCheckModel<>(getItems(), itemBooleanMap)); + itemsProperty().addListener(ov -> { + setCheckModel(new CheckListViewBitSetCheckModel<>(getItems(), itemBooleanMap)); + }); + + setCellFactory(listView -> new CheckBoxListCell<>(new Callback<T, ObservableValue<Boolean>>() { + @Override public ObservableValue<Boolean> call(T item) { + return getItemBooleanProperty(item); + } + })); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * Returns the {@link BooleanProperty} for a given item index in the + * CheckListView. This is useful if you want to bind to the property. + */ + public BooleanProperty getItemBooleanProperty(int index) { + if (index < 0 || index >= getItems().size()) return null; + return getItemBooleanProperty(getItems().get(index)); + } + + /** + * Returns the {@link BooleanProperty} for a given item in the + * CheckListView. This is useful if you want to bind to the property. + */ + public BooleanProperty getItemBooleanProperty(T item) { + return itemBooleanMap.get(item); + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- Check Model + private ObjectProperty<IndexedCheckModel<T>> checkModel = + new SimpleObjectProperty<>(this, "checkModel"); //$NON-NLS-1$ + + /** + * Sets the 'check model' to be used in the CheckListView - this is the + * code that is responsible for representing the selected state of each + * {@link CheckBox} - that is, whether each {@link CheckBox} is checked or + * not (and not to be confused with the + * selection model concept, which is used in the ListView control to + * represent the selection state of each row).. + */ + public final void setCheckModel(IndexedCheckModel<T> value) { + checkModelProperty().set(value); + } + + /** + * Returns the currently installed check model. + */ + public final IndexedCheckModel<T> getCheckModel() { + return checkModel == null ? null : checkModel.get(); + } + + /** + * The check model provides the API through which it is possible + * to check single or multiple items within a CheckListView, as well as inspect + * which items have been checked by the user. Note that it has a generic + * type that must match the type of the CheckListView itself. + */ + public final ObjectProperty<IndexedCheckModel<T>> checkModelProperty() { + return checkModel; + } + + + + /************************************************************************** + * + * Implementation + * + **************************************************************************/ + + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + private static class CheckListViewBitSetCheckModel<T> extends CheckBitSetModelBase<T> { + + /*********************************************************************** + * * + * Internal properties * + * * + **********************************************************************/ + + private final ObservableList<T> items; + + + + /*********************************************************************** + * * + * Constructors * + * * + **********************************************************************/ + + CheckListViewBitSetCheckModel(final ObservableList<T> items, final Map<T, BooleanProperty> itemBooleanMap) { + super(itemBooleanMap); + + this.items = items; + this.items.addListener(new ListChangeListener<T>() { + @Override public void onChanged(Change<? extends T> c) { + updateMap(); + } + }); + + updateMap(); + } + + + + /*********************************************************************** + * * + * Implementing abstract API * + * * + **********************************************************************/ + + @Override public T getItem(int index) { + return items.get(index); + } + + @Override public int getItemCount() { + return items.size(); + } + + @Override public int getItemIndex(T item) { + return items.indexOf(item); + } + } +} diff --git a/src/org/controlsfx/control/CheckModel.java b/src/org/controlsfx/control/CheckModel.java new file mode 100644 index 0000000000000000000000000000000000000000..c305ae2a4fd5bf6b8705c6081d957e33eed5416c --- /dev/null +++ b/src/org/controlsfx/control/CheckModel.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import javafx.collections.ObservableList; + +public interface CheckModel<T> { + + /** + * Returns the count of items in the control. + */ + public int getItemCount(); + + /** + * Returns a read-only list of the currently checked items in the control. + */ + public ObservableList<T> getCheckedItems(); + + /** + * Checks all items in the control + */ + public void checkAll(); + + public void clearCheck(T item); + + /** + * Unchecks all items in the control + */ + public void clearChecks(); + + /** + * Returns true if there are no checked items in the control. + */ + public boolean isEmpty(); + + public boolean isChecked(T item); + + /** + * Checks the given item in the control. + */ + public void check(T item); +} diff --git a/src/org/controlsfx/control/CheckTreeView.java b/src/org/controlsfx/control/CheckTreeView.java new file mode 100644 index 0000000000000000000000000000000000000000..d8b8689a7aa7511e11d471060f1fb87e6538527f --- /dev/null +++ b/src/org/controlsfx/control/CheckTreeView.java @@ -0,0 +1,327 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.CheckBox; +import javafx.scene.control.CheckBoxTreeItem; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; +import javafx.scene.control.cell.CheckBoxTreeCell; + +/** + * A simple UI control that makes it possible to select zero or more items within + * a TreeView without the need to set a custom cell factory or manually create + * boolean properties for each row - simply use the + * {@link #checkModelProperty() check model} to request the current selection + * state. + * + * <h3>Screenshot</h3> + * <p>The following screenshot shows the CheckTreeView with some sample data: + * + * <br> + * <img src="checkTreeView.png" alt="Screenshot of CheckTreeView"> + * + * <h3>Code Example:</h3> + * <p>To create the CheckTreeView shown in the screenshot, simply do the + * following: + * + * <pre> + * {@code + * // create the data to show in the CheckTreeView + * CheckBoxTreeItem<String> root = new CheckBoxTreeItem<String>("Root"); + * root.setExpanded(true); + * root.getChildren().addAll( + * new CheckBoxTreeItem<String>("Jonathan"), + * new CheckBoxTreeItem<String>("Eugene"), + * new CheckBoxTreeItem<String>("Henri"), + * new CheckBoxTreeItem<String>("Samir")); + * + * // Create the CheckTreeView with the data + * final CheckTreeView<String> checkTreeView = new CheckTreeView<>(root); + * + * // and listen to the relevant events (e.g. when the checked items change). + * checkTreeView.getCheckModel().getCheckedItems().addListener(new ListChangeListener<TreeItem<String>>() { + * public void onChanged(ListChangeListener.Change<? extends TreeItem<String>> c) { + * System.out.println(checkTreeView.getCheckModel().getCheckedItems()); + * } + * }); + * }</pre> + * + * @param <T> The type of the data in the TreeView. + */ +public class CheckTreeView<T> extends TreeView<T> { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a new CheckTreeView instance with an empty tree of choices. + */ + public CheckTreeView() { + this(null); + } + + /** + * Creates a new CheckTreeView instance with the given CheckBoxTreeItem set + * as the tree root. + * + * @param root The root tree item to display in the CheckTreeView. + */ + public CheckTreeView(final CheckBoxTreeItem<T> root) { + super(root); + rootProperty().addListener(o -> updateCheckModel()); + + updateCheckModel(); + + setCellFactory(CheckBoxTreeCell.<T> forTreeView()); + } + + protected void updateCheckModel() { + if (getRoot() != null) { + setCheckModel(new CheckTreeViewCheckModel<>(this)); + } + } + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * Returns the {@link BooleanProperty} for a given item index in the + * CheckTreeView. This is useful if you want to bind to the property. + */ + public BooleanProperty getItemBooleanProperty(int index) { + CheckBoxTreeItem<T> treeItem = (CheckBoxTreeItem<T>) getTreeItem(index); + return treeItem.selectedProperty(); + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- Check Model + private ObjectProperty<CheckModel<TreeItem<T>>> checkModel = + new SimpleObjectProperty<>(this, "checkModel"); //$NON-NLS-1$ + + /** + * Sets the 'check model' to be used in the CheckTreeView - this is the + * code that is responsible for representing the selected state of each + * {@link CheckBox} - that is, whether each {@link CheckBox} is checked or + * not (and not to be confused with the + * selection model concept, which is used in the TreeView control to + * represent the selection state of each row).. + */ + public final void setCheckModel(CheckModel<TreeItem<T>> value) { + checkModelProperty().set(value); + } + + /** + * Returns the currently installed check model. + */ + public final CheckModel<TreeItem<T>> getCheckModel() { + return checkModel == null ? null : checkModel.get(); + } + + /** + * The check model provides the API through which it is possible + * to check single or multiple items within a CheckTreeView, as well as inspect + * which items have been checked by the user. Note that it has a generic + * type that must match the type of the CheckTreeView itself. + */ + public final ObjectProperty<CheckModel<TreeItem<T>>> checkModelProperty() { + return checkModel; + } + + + + /************************************************************************** + * + * Implementation + * + **************************************************************************/ + + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + private static class CheckTreeViewCheckModel<T> implements CheckModel<TreeItem<T>> {// extends CheckBitSetModelBase<TreeItem<T>> { + + /*********************************************************************** + * * + * Internal properties * + * * + **********************************************************************/ + + private final CheckTreeView<T> treeView; + private final TreeItem<T> root; + + private ObservableList<TreeItem<T>> checkedItems = FXCollections.observableArrayList(); + + + + /*********************************************************************** + * * + * Constructors * + * * + **********************************************************************/ + + CheckTreeViewCheckModel(final CheckTreeView<T> treeView) { + this.treeView = treeView; + this.root = treeView.getRoot(); + this.root.addEventHandler(CheckBoxTreeItem.<T>checkBoxSelectionChangedEvent(), e -> { + CheckBoxTreeItem<T> treeItem = e.getTreeItem(); + + if (treeItem.isSelected()) { // && ! treeItem.isIndeterminate()) { + check(treeItem); + } else { + clearCheck(treeItem); + } + }); + + // we should reset the check model and then update the checked items + // based on the currently checked items in the tree view + clearChecks(); + for (int i = 0; i < treeView.getExpandedItemCount(); i++) { + CheckBoxTreeItem<T> treeItem = (CheckBoxTreeItem<T>) treeView.getTreeItem(i); + if (treeItem.isSelected() && ! treeItem.isIndeterminate()) { + check(treeItem); + } + } + } + + + + /*********************************************************************** + * * + * Implementing abstract API * + * * + **********************************************************************/ + + @Override public int getItemCount() { + return treeView.getExpandedItemCount(); + } + + + // TODO make read-only + @Override public ObservableList<TreeItem<T>> getCheckedItems() { + return checkedItems; + } + + @Override public void checkAll() { + iterateOverTree(this::check); + } + + @Override public void clearCheck(TreeItem<T> item) { + if (item instanceof CheckBoxTreeItem) { + ((CheckBoxTreeItem<T>)item).setSelected(false); + } + checkedItems.remove(item); + } + + @Override public void clearChecks() { + List<TreeItem<T>> items = new ArrayList<>(checkedItems); + for(TreeItem<T> item : items){ + clearCheck(item); + } + } + + @Override public boolean isEmpty() { + return checkedItems.isEmpty(); + } + + @Override public boolean isChecked(TreeItem<T> item) { + return checkedItems.contains(item); + } + + @Override public void check(TreeItem<T> item) { + if (item instanceof CheckBoxTreeItem) { + ((CheckBoxTreeItem<T>)item).setSelected(true); + } + if (!checkedItems.contains(item)) { + checkedItems.add(item); + } + } + + + + /*********************************************************************** + * * + * Private Implementation * + * * + **********************************************************************/ + + private void iterateOverTree(Consumer<TreeItem<T>> consumer) { + processNode(consumer, root); + } + + private void processNode(Consumer<TreeItem<T>> consumer, TreeItem<T> node) { + if (node == null) return; + consumer.accept(node); + processChildren(consumer, node.getChildren()); + } + + private void processChildren(Consumer<TreeItem<T>> consumer, List<TreeItem<T>> children) { + if (children == null) return; + for (TreeItem<T> child : children) { + processNode(consumer, child); + } + } + } +} diff --git a/src/org/controlsfx/control/ControlsFXControl.java b/src/org/controlsfx/control/ControlsFXControl.java new file mode 100644 index 0000000000000000000000000000000000000000..15b3057358945566ab2b13f7b97caadef3ac3bc6 --- /dev/null +++ b/src/org/controlsfx/control/ControlsFXControl.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.version.VersionChecker; +import javafx.scene.control.Control; + +abstract class ControlsFXControl extends Control { + + public ControlsFXControl() { + VersionChecker.doVersionCheck(); + } + + private String stylesheet; + + /** + * A helper method that ensures that the resource based lookup of the user + * agent stylesheet only happens once. Caches the external form of the + * resource. + * + * @param clazz + * the class used for the resource lookup + * @param fileName + * the name of the user agent stylesheet + * @return the external form of the user agent stylesheet (the path) + */ + protected final String getUserAgentStylesheet(Class<?> clazz, + String fileName) { + + /* + * For more information please see RT-40658 + */ + if (stylesheet == null) { + stylesheet = clazz.getResource(fileName).toExternalForm(); + } + + return stylesheet; + } +} diff --git a/src/org/controlsfx/control/GridCell.java b/src/org/controlsfx/control/GridCell.java new file mode 100644 index 0000000000000000000000000000000000000000..158ec896ca396b822ec1cbfa192c5b35aae4fb5f --- /dev/null +++ b/src/org/controlsfx/control/GridCell.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.GridCellSkin; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.IndexedCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Skin; +import javafx.scene.control.TableView; + +/** + * A GridCell is created to represent items in the {@link GridView} + * {@link GridView#getItems() items list}. As with other JavaFX UI controls + * (like {@link ListView}, {@link TableView}, etc), the {@link GridView} control + * is virtualised, meaning it is exceedingly memory and CPU efficient. Refer to + * the {@link GridView} class documentation for more details. + * + * @see GridView + */ +public class GridCell<T> extends IndexedCell<T> { + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a default GridCell instance. + */ + public GridCell() { + getStyleClass().add("grid-cell"); //$NON-NLS-1$ + +// itemProperty().addListener(new ChangeListener<T>() { +// @Override public void changed(ObservableValue<? extends T> arg0, T oldItem, T newItem) { +// updateItem(newItem, newItem == null); +// } +// }); + + // TODO listen for index change and update index and item, rather than + // listen to just item update as above. This requires the GridCell to + // know about its containing GridRow (and the GridRow to know its + // containing GridView) + indexProperty().addListener(new InvalidationListener() { + @Override public void invalidated(Observable observable) { + final GridView<T> gridView = getGridView(); + if (gridView == null) return; + + if(getIndex() < 0) { + updateItem(null, true); + return; + } + T item = gridView.getItems().get(getIndex()); + +// updateIndex(getIndex()); + updateItem(item, item == null); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new GridCellSkin<>(this); + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + /** + * The {@link GridView} that this GridCell exists within. + */ + public SimpleObjectProperty<GridView<T>> gridViewProperty() { + return gridView; + } + private final SimpleObjectProperty<GridView<T>> gridView = + new SimpleObjectProperty<>(this, "gridView"); //$NON-NLS-1$ + + /** + * Sets the {@link GridView} that this GridCell exists within. + */ + public final void updateGridView(GridView<T> gridView) { + this.gridView.set(gridView); + } + + /** + * Returns the {@link GridView} that this GridCell exists within. + */ + public GridView<T> getGridView() { + return gridView.get(); + } +} \ No newline at end of file diff --git a/src/org/controlsfx/control/GridView.java b/src/org/controlsfx/control/GridView.java new file mode 100644 index 0000000000000000000000000000000000000000..c575fe36b09cc496ba6a6d4975e2d4ba87db5607 --- /dev/null +++ b/src/org/controlsfx/control/GridView.java @@ -0,0 +1,560 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.GridViewSkin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.css.CssMetaData; +import javafx.css.StyleConverter; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableProperty; +import javafx.scene.Node; +import javafx.scene.control.Cell; +import javafx.scene.control.Control; +import javafx.scene.control.ListCell; +import javafx.scene.control.Skin; +import javafx.scene.paint.Color; +import javafx.util.Callback; + +import org.controlsfx.control.cell.ColorGridCell; + +/** + * A GridView is a virtualised control for displaying {@link #getItems()} in a + * visual, scrollable, grid-like fashion. In other words, whereas a ListView + * shows one {@link ListCell} per row, in a GridView there will be zero or more + * {@link GridCell} instances on a single row. + * + * <p> This approach means that the number of GridCell instances + * instantiated will be a significantly smaller number than the number of + * items in the GridView items list, as only enough GridCells are created for + * the visible area of the GridView. This helps to improve performance and + * reduce memory consumption. + * + * <p>Because each {@link GridCell} extends from {@link Cell}, the same approach + * of cell factories that is taken in other UI controls is also taken in GridView. + * This has two main benefits: + * + * <ol> + * <li>GridCells are created on demand and without user involvement, + * <li>GridCells can be arbitrarily complex. A simple GridCell may just have + * its {@link GridCell#textProperty() text property} set, whereas a more complex + * GridCell can have an arbitrarily complex scenegraph set inside its + * {@link GridCell#graphicProperty() graphic property} (as it accepts any Node). + * </ol> + * + * <h3>Examples</h3> + * <p>The following screenshot shows the GridView with the {@link ColorGridCell} + * being used: + * + * <br> + * <img src="gridView.png" alt="Screenshot of GridView"> + * + * <p>To create this GridView was simple. Note that the majority of the code below + * is related to randomly creating the colours to be represented: + * + * <pre> + * {@code + * GridView<Color> myGrid = new GridView<>(list); + * myGrid.setCellFactory(new Callback<GridView<Color>, GridCell<Color>>() { + * public GridCell<Color> call(GridView<Color> gridView) { + * return new ColorGridCell(); + * } + * }); + * Random r = new Random(System.currentTimeMillis()); + * for(int i = 0; i < 500; i++) { + * list.add(new Color(r.nextDouble(), r.nextDouble(), r.nextDouble(), 1.0)); + * } + * }</pre> + * + * @see GridCell + */ +public class GridView<T> extends ControlsFXControl { + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a default, empty GridView control. + */ + public GridView() { + this(FXCollections.<T> observableArrayList()); + } + + /** + * Creates a default GridView control with the provided items prepopulated. + * + * @param items The items to display inside the GridView. + */ + public GridView(ObservableList<T> items) { + getStyleClass().add(DEFAULT_STYLE_CLASS); + setItems(items); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new GridViewSkin<>(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(GridView.class, "gridview.css"); + } + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- horizontal cell spacing + /** + * Property for specifying how much spacing there is between each cell + * in a row (i.e. how much horizontal spacing there is). + */ + public final DoubleProperty horizontalCellSpacingProperty() { + if (horizontalCellSpacing == null) { + horizontalCellSpacing = new StyleableDoubleProperty(12) { + @Override public CssMetaData<GridView<?>, Number> getCssMetaData() { + return GridView.StyleableProperties.HORIZONTAL_CELL_SPACING; + } + + @Override public Object getBean() { + return GridView.this; + } + + @Override public String getName() { + return "horizontalCellSpacing"; //$NON-NLS-1$ + } + }; + } + return horizontalCellSpacing; + } + private DoubleProperty horizontalCellSpacing; + + /** + * Sets the amount of horizontal spacing there should be between cells in + * the same row. + * @param value The amount of spacing to use. + */ + public final void setHorizontalCellSpacing(double value) { + horizontalCellSpacingProperty().set(value); + } + + /** + * Returns the amount of horizontal spacing there is between cells in + * the same row. + */ + public final double getHorizontalCellSpacing() { + return horizontalCellSpacing == null ? 12.0 : horizontalCellSpacing.get(); + } + + + + // --- vertical cell spacing + /** + * Property for specifying how much spacing there is between each cell + * in a column (i.e. how much vertical spacing there is). + */ + private DoubleProperty verticalCellSpacing; + public final DoubleProperty verticalCellSpacingProperty() { + if (verticalCellSpacing == null) { + verticalCellSpacing = new StyleableDoubleProperty(12) { + @Override public CssMetaData<GridView<?>, Number> getCssMetaData() { + return GridView.StyleableProperties.VERTICAL_CELL_SPACING; + } + + @Override public Object getBean() { + return GridView.this; + } + + @Override public String getName() { + return "verticalCellSpacing"; //$NON-NLS-1$ + } + }; + } + return verticalCellSpacing; + } + + /** + * Sets the amount of vertical spacing there should be between cells in + * the same column. + * @param value The amount of spacing to use. + */ + public final void setVerticalCellSpacing(double value) { + verticalCellSpacingProperty().set(value); + } + + /** + * Returns the amount of vertical spacing there is between cells in + * the same column. + */ + public final double getVerticalCellSpacing() { + return verticalCellSpacing == null ? 12.0 : verticalCellSpacing.get(); + } + + + + // --- cell width + /** + * Property representing the width that all cells should be. + */ + public final DoubleProperty cellWidthProperty() { + if (cellWidth == null) { + cellWidth = new StyleableDoubleProperty(64) { + @Override public CssMetaData<GridView<?>, Number> getCssMetaData() { + return GridView.StyleableProperties.CELL_WIDTH; + } + + @Override public Object getBean() { + return GridView.this; + } + + @Override public String getName() { + return "cellWidth"; //$NON-NLS-1$ + } + }; + } + return cellWidth; + } + private DoubleProperty cellWidth; + + /** + * Sets the width that all cells should be. + */ + public final void setCellWidth(double value) { + cellWidthProperty().set(value); + } + + /** + * Returns the width that all cells should be. + */ + public final double getCellWidth() { + return cellWidth == null ? 64.0 : cellWidth.get(); + } + + + // --- cell height + /** + * Property representing the height that all cells should be. + */ + public final DoubleProperty cellHeightProperty() { + if (cellHeight == null) { + cellHeight = new StyleableDoubleProperty(64) { + @Override public CssMetaData<GridView<?>, Number> getCssMetaData() { + return GridView.StyleableProperties.CELL_HEIGHT; + } + + @Override public Object getBean() { + return GridView.this; + } + + @Override public String getName() { + return "cellHeight"; //$NON-NLS-1$ + } + }; + } + return cellHeight; + } + private DoubleProperty cellHeight; + + /** + * Sets the height that all cells should be. + */ + public final void setCellHeight(double value) { + cellHeightProperty().set(value); + } + + /** + * Returns the height that all cells should be. + */ + public final double getCellHeight() { + return cellHeight == null ? 64.0 : cellHeight.get(); + } + + + // I've removed this functionality until there is a clear need for it. + // To re-enable it, there is code in GridRowSkin that has been commented + // out that must be re-enabled. + // Don't forget also to enable the styleable property further down in this + // class. +// // --- horizontal alignment +// private ObjectProperty<HPos> horizontalAlignment; +// public final ObjectProperty<HPos> horizontalAlignmentProperty() { +// if (horizontalAlignment == null) { +// horizontalAlignment = new StyleableObjectProperty<HPos>(HPos.CENTER) { +// @Override public CssMetaData<GridView<?>,HPos> getCssMetaData() { +// return GridView.StyleableProperties.HORIZONTAL_ALIGNMENT; +// } +// +// @Override public Object getBean() { +// return GridView.this; +// } +// +// @Override public String getName() { +// return "horizontalAlignment"; +// } +// }; +// } +// return horizontalAlignment; +// } +// +// public final void setHorizontalAlignment(HPos value) { +// horizontalAlignmentProperty().set(value); +// } +// +// public final HPos getHorizontalAlignment() { +// return horizontalAlignment == null ? HPos.CENTER : horizontalAlignment.get(); +// } + + + // --- cell factory + /** + * Property representing the cell factory that is currently set in this + * GridView, or null if no cell factory has been set (in which case the + * default cell factory provided by the GridView skin will be used). The cell + * factory is used for instantiating enough GridCell instances for the + * visible area of the GridView. Refer to the GridView class documentation + * for more information and examples. + */ + public final ObjectProperty<Callback<GridView<T>, GridCell<T>>> cellFactoryProperty() { + if (cellFactory == null) { + cellFactory = new SimpleObjectProperty<>(this, "cellFactory"); //$NON-NLS-1$ + } + return cellFactory; + } + private ObjectProperty<Callback<GridView<T>, GridCell<T>>> cellFactory; + + /** + * Sets the cell factory to use to create {@link GridCell} instances to + * show in the GridView. + */ + public final void setCellFactory(Callback<GridView<T>, GridCell<T>> value) { + cellFactoryProperty().set(value); + } + + /** + * Returns the cell factory that will be used to create {@link GridCell} + * instances to show in the GridView. + */ + public final Callback<GridView<T>, GridCell<T>> getCellFactory() { + return cellFactory == null ? null : cellFactory.get(); + } + + + // --- items + /** + * The items to be displayed in the GridView (as rendered via {@link GridCell} + * instances). For example, if the {@link ColorGridCell} were being used + * (as in the case at the top of this class documentation), this items list + * would be populated with {@link Color} values. It is important to + * appreciate that the items list is used for the data, not the rendering. + * What is meant by this is that the items list should contain Color values, + * not the {@link Node nodes} that represent the Color. The actual rendering + * should be left up to the {@link #cellFactoryProperty() cell factory}, + * where it will take the Color value and create / update the display as + * necessary. + */ + public final ObjectProperty<ObservableList<T>> itemsProperty() { + if (items == null) { + items = new SimpleObjectProperty<>(this, "items"); //$NON-NLS-1$ + } + return items; + } + private ObjectProperty<ObservableList<T>> items; + + /** + * Sets a new {@link ObservableList} as the items list underlying GridView. + * The old items list will be discarded. + */ + public final void setItems(ObservableList<T> value) { + itemsProperty().set(value); + } + + /** + * Returns the currently-in-use items list that is being used by the + * GridView. + */ + public final ObservableList<T> getItems() { + return items == null ? null : items.get(); + } + + + + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "grid-view"; //$NON-NLS-1$ + + /** @treatAsPrivate */ + private static class StyleableProperties { + private static final CssMetaData<GridView<?>,Number> HORIZONTAL_CELL_SPACING = + new CssMetaData<GridView<?>,Number>("-fx-horizontal-cell-spacing", StyleConverter.getSizeConverter(), 12d) { //$NON-NLS-1$ + + @Override public Double getInitialValue(GridView<?> node) { + return node.getHorizontalCellSpacing(); + } + + @Override public boolean isSettable(GridView<?> n) { + return n.horizontalCellSpacing == null || !n.horizontalCellSpacing.isBound(); + } + + @Override + @SuppressWarnings("unchecked") + public StyleableProperty<Number> getStyleableProperty(GridView<?> n) { + return (StyleableProperty<Number>)n.horizontalCellSpacingProperty(); + } + }; + + private static final CssMetaData<GridView<?>,Number> VERTICAL_CELL_SPACING = + new CssMetaData<GridView<?>,Number>("-fx-vertical-cell-spacing", StyleConverter.getSizeConverter(), 12d) { //$NON-NLS-1$ + + @Override public Double getInitialValue(GridView<?> node) { + return node.getVerticalCellSpacing(); + } + + @Override public boolean isSettable(GridView<?> n) { + return n.verticalCellSpacing == null || !n.verticalCellSpacing.isBound(); + } + + @Override + @SuppressWarnings("unchecked") + public StyleableProperty<Number> getStyleableProperty(GridView<?> n) { + return (StyleableProperty<Number>)n.verticalCellSpacingProperty(); + } + }; + + private static final CssMetaData<GridView<?>,Number> CELL_WIDTH = + new CssMetaData<GridView<?>,Number>("-fx-cell-width", StyleConverter.getSizeConverter(), 64d) { //$NON-NLS-1$ + + @Override public Double getInitialValue(GridView<?> node) { + return node.getCellWidth(); + } + + @Override public boolean isSettable(GridView<?> n) { + return n.cellWidth == null || !n.cellWidth.isBound(); + } + + @Override + @SuppressWarnings("unchecked") + public StyleableProperty<Number> getStyleableProperty(GridView<?> n) { + return (StyleableProperty<Number>)n.cellWidthProperty(); + } + }; + + private static final CssMetaData<GridView<?>,Number> CELL_HEIGHT = + new CssMetaData<GridView<?>,Number>("-fx-cell-height", StyleConverter.getSizeConverter(), 64d) { //$NON-NLS-1$ + + @Override public Double getInitialValue(GridView<?> node) { + return node.getCellHeight(); + } + + @Override public boolean isSettable(GridView<?> n) { + return n.cellHeight == null || !n.cellHeight.isBound(); + } + + @Override + @SuppressWarnings("unchecked") + public StyleableProperty<Number> getStyleableProperty(GridView<?> n) { + return (StyleableProperty<Number>)n.cellHeightProperty(); + } + }; + +// private static final CssMetaData<GridView<?>,HPos> HORIZONTAL_ALIGNMENT = +// new CssMetaData<GridView<?>,HPos>("-fx-horizontal_alignment", +// new EnumConverter<HPos>(HPos.class), +// HPos.CENTER) { +// +// @Override public HPos getInitialValue(GridView node) { +// return node.getHorizontalAlignment(); +// } +// +// @Override public boolean isSettable(GridView n) { +// return n.horizontalAlignment == null || !n.horizontalAlignment.isBound(); +// } +// +// @Override public StyleableProperty<HPos> getStyleableProperty(GridView n) { +// return (StyleableProperty<HPos>)n.horizontalAlignmentProperty(); +// } +// }; + + private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; + static { + final List<CssMetaData<? extends Styleable, ?>> styleables = + new ArrayList<>(Control.getClassCssMetaData()); + styleables.add(HORIZONTAL_CELL_SPACING); + styleables.add(VERTICAL_CELL_SPACING); + styleables.add(CELL_WIDTH); + styleables.add(CELL_HEIGHT); +// styleables.add(HORIZONTAL_ALIGNMENT); + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + /** + * @return The CssMetaData associated with this class, which may include the + * CssMetaData of its super classes. + */ + public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + /** + * {@inheritDoc} + */ + @Override + public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { + return getClassCssMetaData(); + } +} diff --git a/src/org/controlsfx/control/HiddenSidesPane.java b/src/org/controlsfx/control/HiddenSidesPane.java new file mode 100644 index 0000000000000000000000000000000000000000..9aa37cb299ce78b3041bc7c64631a5c45f29dbd2 --- /dev/null +++ b/src/org/controlsfx/control/HiddenSidesPane.java @@ -0,0 +1,426 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.HiddenSidesPaneSkin; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Side; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.util.Duration; + +/** + * A pane used to display a full-size content node and four initially hidden + * nodes on the four sides. The hidden nodes can be made visible by moving the + * mouse cursor to the edges (see {@link #setTriggerDistance(double)}) of the + * pane. The hidden node will appear (at its preferred width or height) with a + * short slide-in animation. The node will disappear again as soon as the mouse + * cursor exits it. A hidden node / side can also be pinned by calling + * {@link #setPinnedSide(Side)}. It will remain visible as long as it stays + * pinned. + * + * <h3>Screenshot</h3> The following screenshots shows the right side node + * hovering over a table after it was made visible: + * + * <center><img src="hiddenSidesPane.png" alt="Screenshot of HiddenSidesPane"> + * + * </center> <h3>Code Sample</h3> + * + * <pre> + * HiddenSidesPane pane = new HiddenSidesPane(); + * pane.setContent(new TableView()); + * pane.setRight(new ListView()); + * </pre> + */ +public class HiddenSidesPane extends ControlsFXControl { + + /** + * Constructs a new pane with the given content node and the four side + * nodes. Each one of the side nodes may be null. + * + * @param content + * the primary node that will fill the entire width and height of + * the pane + * @param top + * the hidden node on the top side + * @param right + * the hidden node on the right side + * @param bottom + * the hidden node on the bottom side + * @param left + * the hidden node on the left side + */ + public HiddenSidesPane(Node content, Node top, Node right, Node bottom, + Node left) { + setContent(content); + setTop(top); + setRight(right); + setBottom(bottom); + setLeft(left); + } + + /** + * Constructs a new pane with no content and no side nodes. + */ + public HiddenSidesPane() { + this(null, null, null, null, null); + } + + @Override + protected Skin<?> createDefaultSkin() { + return new HiddenSidesPaneSkin(this); + } + + private DoubleProperty triggerDistance = new SimpleDoubleProperty(this, + "triggerDistance", 16); //$NON-NLS-1$ + + /** + * The property that stores the distance to the pane's edges that will + * trigger the appearance of the hidden side nodes.<br> + * Setting the property to zero or a negative value will disable this + * functionality, so a hidden side can only be made visible with + * {@link #setPinnedSide(Side)}. + * + * @return the trigger distance property + */ + public final DoubleProperty triggerDistanceProperty() { + return triggerDistance; + } + + /** + * Returns the value of the trigger distance property. + * + * @return the trigger distance property value + */ + public final double getTriggerDistance() { + return triggerDistance.get(); + } + + /** + * Set the value of the trigger distance property. <br> + * Setting the property to zero or a negative value will disable this + * functionality, so a hidden side can only be made visible with + * {@link #setPinnedSide(Side)}. + * + * @param distance + * the new value for the trigger distance property + */ + public final void setTriggerDistance(double distance) { + triggerDistance.set(distance); + } + + // Content node support. + + private ObjectProperty<Node> content = new SimpleObjectProperty<>(this, + "content"); //$NON-NLS-1$ + + /** + * The property that is used to store a reference to the content node. The + * content node will fill the entire width and height of the pane. + * + * @return the content node property + */ + public final ObjectProperty<Node> contentProperty() { + return content; + } + + /** + * Returns the value of the content node property. + * + * @return the content node property value + */ + public final Node getContent() { + return contentProperty().get(); + } + + /** + * Sets the value of the content node property. + * + * @param content + * the new content node + */ + public final void setContent(Node content) { + contentProperty().set(content); + } + + // Top node support. + + private ObjectProperty<Node> top = new SimpleObjectProperty<>(this, + "top"); //$NON-NLS-1$ + + /** + * The property used to store a reference to the node shown at the top side + * of the pane. + * + * @return the hidden node at the top side of the pane + */ + public final ObjectProperty<Node> topProperty() { + return top; + } + + /** + * Returns the value of the top node property. + * + * @return the top node property value + */ + public final Node getTop() { + return topProperty().get(); + } + + /** + * Sets the value of the top node property. + * + * @param top + * the top node value + */ + public final void setTop(Node top) { + topProperty().set(top); + } + + // Right node support. + + /** + * The property used to store a reference to the node shown at the right + * side of the pane. + * + * @return the hidden node at the right side of the pane + */ + private ObjectProperty<Node> right = new SimpleObjectProperty<>(this, + "right"); //$NON-NLS-1$ + + /** + * Returns the value of the right node property. + * + * @return the right node property value + */ + public final ObjectProperty<Node> rightProperty() { + return right; + } + + /** + * Returns the value of the right node property. + * + * @return the right node property value + */ + public final Node getRight() { + return rightProperty().get(); + } + + /** + * Sets the value of the right node property. + * + * @param right + * the right node value + */ + public final void setRight(Node right) { + rightProperty().set(right); + } + + // Bottom node support. + + /** + * The property used to store a reference to the node shown at the bottom + * side of the pane. + * + * @return the hidden node at the bottom side of the pane + */ + private ObjectProperty<Node> bottom = new SimpleObjectProperty<>(this, + "bottom"); //$NON-NLS-1$ + + /** + * Returns the value of the bottom node property. + * + * @return the bottom node property value + */ + public final ObjectProperty<Node> bottomProperty() { + return bottom; + } + + /** + * Returns the value of the bottom node property. + * + * @return the bottom node property value + */ + public final Node getBottom() { + return bottomProperty().get(); + } + + /** + * Sets the value of the bottom node property. + * + * @param bottom + * the bottom node value + */ + public final void setBottom(Node bottom) { + bottomProperty().set(bottom); + } + + // Left node support. + + /** + * The property used to store a reference to the node shown at the left side + * of the pane. + * + * @return the hidden node at the left side of the pane + */ + private ObjectProperty<Node> left = new SimpleObjectProperty<>(this, + "left"); //$NON-NLS-1$ + + /** + * Returns the value of the left node property. + * + * @return the left node property value + */ + public final ObjectProperty<Node> leftProperty() { + return left; + } + + /** + * Returns the value of the left node property. + * + * @return the left node property value + */ + public final Node getLeft() { + return leftProperty().get(); + } + + /** + * Sets the value of the left node property. + * + * @param left + * the left node value + */ + public final void setLeft(Node left) { + leftProperty().set(left); + } + + // Pinned side support. + + private ObjectProperty<Side> pinnedSide = new SimpleObjectProperty<>( + this, "pinnedSide"); //$NON-NLS-1$ + + /** + * Returns the pinned side property. The value of this property determines + * if one of the four hidden sides stays visible all the time. + * + * @return the pinned side property + */ + public final ObjectProperty<Side> pinnedSideProperty() { + return pinnedSide; + } + + /** + * Returns the value of the pinned side property. + * + * @return the pinned side property value + */ + public final Side getPinnedSide() { + return pinnedSideProperty().get(); + } + + /** + * Sets the value of the pinned side property. + * + * @param side + * the new pinned side value + */ + public final void setPinnedSide(Side side) { + pinnedSideProperty().set(side); + } + + // slide in animation delay + + private final ObjectProperty<Duration> animationDelay = new SimpleObjectProperty<>( + this, "animationDelay", Duration.millis(300)); //$NON-NLS-1$ + + /** + * Returns the animation delay property. The value of this property + * determines the delay before the hidden side slide in / slide out + * animation starts to play. + * + * @return animation delay property + */ + public final ObjectProperty<Duration> animationDelayProperty() { + return animationDelay; + } + + /** + * Returns the animation delay + * + * @return animation delay + */ + public final Duration getAnimationDelay() { + return animationDelay.get(); + } + + /** + * Set the animation delay + * + * @param duration + * slide in animation delay + */ + public final void setAnimationDelay(Duration duration) { + animationDelay.set(duration); + } + + // slide in / slide out duration + + private final ObjectProperty<Duration> animationDuration = new SimpleObjectProperty<>( + this, "animationDuration", Duration.millis(200)); //$NON-NLS-1$ + + /** + * Returns the animation duration property. The value of this property + * determines the fade in time for a hidden side to become visible. + * + * @return animation delay property + */ + public final ObjectProperty<Duration> animationDurationProperty() { + return animationDuration; + } + + /** + * Returns the animation delay + * + * @return animation delay + */ + public final Duration getAnimationDuration() { + return animationDuration.get(); + } + + /** + * Set the animation delay + * + * @param duration + * animation duration + */ + public final void setAnimationDuration(Duration duration) { + animationDuration.set(duration); + } +} diff --git a/src/org/controlsfx/control/HyperlinkLabel.java b/src/org/controlsfx/control/HyperlinkLabel.java new file mode 100644 index 0000000000000000000000000000000000000000..abf3b90ef77d724ecfbdcb97c5a340ae235baf9e --- /dev/null +++ b/src/org/controlsfx/control/HyperlinkLabel.java @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.HyperlinkLabelSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Skin; + +import com.sun.javafx.event.EventHandlerManager; + +/** + * A UI control that will convert the given text into a series of text labels + * and {@link Hyperlink hyperlinks}, based on the use of delimiter characters + * to specify where hyperlinks should appear. The delimiter characters are + * square braces (that is, [ and ]). To create a hyperlink in a string you would + * therefore do something like + * <code>hyperlinkLabel.setText("Click [here] for more information!");</code>, + * with the word 'here' appearing as a hyperlink that a use may click. This + * approach therefore allows for hyperlinks to be easily embedded within a + * label. + * + * <p>Once hyperlinks have been declared in a text string, it is necessary to + * respond to the user interacting with the hyperlink (most commonly via mouse + * clicks). To do so, you register a single event handler for action events on + * the HyperlinkLabel instance, and then determine what to do within that + * callback. For example: + * + * <pre> + * {@code + * hyperlinkLabel.setOnAction(new EventHandler<ActionEvent>() { + * public void handle(ActionEvent event) { + * Hyperlink link = (Hyperlink)event.getSource(); + * final String str = link == null ? "" : link.getText(); + * switch(str) { + * case "here": // do 'here' action + * break; + * case "exit": // do exit action + * break; + * } + * } + * });}</pre> + * + * <p>This simple single-handler approach was chosen over any more complex + * per-hyperlink solution because it is anticipated that most use cases will + * normally consist of one, or very few hyperlinks, and it was therefore unlikely + * that the increased API complexity would be warranted. + * + * <h3>Screenshot</h3> + * <p>To demonstrate what a HyperlinkLabel looks like, refer to the screenshot + * below, when the text + * <code>"Hello [world]! I [wonder] what hyperlink [you] [will] [click]"</code> + * was passed in to the HyperlinkLabel instance: + * + * <br><br> + * <center><img src="hyperlinkLabel.PNG" alt="Screenshot of HyperlinkLabel"></center> + * + * @see Hyperlink + * @see ActionEvent + */ +public class HyperlinkLabel extends ControlsFXControl implements EventTarget { + + /*************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private final EventHandlerManager eventHandlerManager = + new EventHandlerManager(this); + + + + /*************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates an empty HyperlinkLabel instance with no {@link #textProperty() text} + * specified. + */ + public HyperlinkLabel() { + this(null); + } + + /** + * Creates a HyperlinkLabel instance with the given text value used as the + * initial text. + * + * @param text The text to display to the user. + */ + public HyperlinkLabel(String text) { + setText(text); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new HyperlinkLabelSkin(this); + } + + + // --- text + private final StringProperty text = new SimpleStringProperty(this, "text"); //$NON-NLS-1$ + + /** + * Return a {@link StringProperty} representing the text being displayed. + * @return a {@link StringProperty}. + */ + public final StringProperty textProperty() { + return text; + } + + /** + * Return the text currently displayed. + * @return the text currently displayed. + */ + public final String getText() { + return text.get(); + } + + /** + * Set a new text to display to the user, using the delimiter characters [ and ] + * to indicate where hyperlinks should be displayed. + * @param value + */ + public final void setText(String value) { + text.set(value); + } + + + // --- onAction + private ObjectProperty<EventHandler<ActionEvent>> onAction; + + /** + * The action, which is invoked whenever a hyperlink is fired. This + * may be due to the user clicking on the hyperlink with the mouse, or by + * a touch event, or by a key press. + * @return an {@link ObjectProperty} representing the action. + */ + public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { + if (onAction == null) { + onAction = new SimpleObjectProperty<EventHandler<ActionEvent>>(this, "onAction") { //$NON-NLS-1$ + @Override protected void invalidated() { + eventHandlerManager.setEventHandler(ActionEvent.ACTION, get()); + } + }; + } + return onAction; + } + + /** + * Sets a new EventHandler which will be invoked whenever a hyperlink is + * fired. + * @param value + */ + public final void setOnAction(EventHandler<ActionEvent> value) { + onActionProperty().set( value); + } + + /** + * + * @return the action, which is invoked whenever a hyperlink is fired. + */ + public final EventHandler<ActionEvent> getOnAction() { + return onAction == null ? null : onAction.get(); + } + + +} diff --git a/src/org/controlsfx/control/IndexedCheckModel.java b/src/org/controlsfx/control/IndexedCheckModel.java new file mode 100644 index 0000000000000000000000000000000000000000..acb77cd76a313ee6c2743657199c9b486d9a3b27 --- /dev/null +++ b/src/org/controlsfx/control/IndexedCheckModel.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import javafx.collections.ObservableList; + +public interface IndexedCheckModel<T> extends CheckModel<T> { + + /** + * Returns the item in the given index in the control. + */ + public T getItem(int index); + + /** + * Returns the index of the given item. + */ + public int getItemIndex(T item); + + /** + * Returns a read-only list of the currently checked indices in the control. + */ + public ObservableList<Integer> getCheckedIndices(); + + /** + * Checks the given indices in the control + */ + public void checkIndices(int... indices); + + /** + * Unchecks the given index in the control + */ + public void clearCheck(int index); + + /** + * Returns true if the given index represents an item that is checked in the control. + */ + public boolean isChecked(int index); + + /** + * Checks the item in the given index in the control. + */ + public void check(int index); + +} \ No newline at end of file diff --git a/src/org/controlsfx/control/InfoOverlay.java b/src/org/controlsfx/control/InfoOverlay.java new file mode 100644 index 0000000000000000000000000000000000000000..3d9cf5d1990fe61fedf4ec278b8dad6b826f83db --- /dev/null +++ b/src/org/controlsfx/control/InfoOverlay.java @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.controlsfx.control; + +import impl.org.controlsfx.skin.InfoOverlaySkin; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.scene.image.ImageView; + +/** + * A simple UI control that allows for an information popup to be displayed over + * a node to describe it in further detail. In some ways, it can be thought of + * as a always visible tooltip (although by default it is collapsed so only the + * first line is shown - clicking on it will expand it to show all text). + * + * <p>Shown below is a screenshot of the InfoOverlay control in both its + * collapsed and expanded states: + * + * <br> + * <center> + * <img src="infoOverlay.png" alt="Screenshot of InfoOverlay"> + * </center> + */ +public class InfoOverlay extends ControlsFXControl { + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Constructs a default InfoOverlay control with no node or text. + */ + public InfoOverlay() { + this((Node)null, null); + } + + /** + * Attempts to construct an InfoOverlay instance using the given string + * to load an image, and to place the given text string over top of it. + * + * @param imageUrl The image file to attempt to load. + * @param text The text to display over top of the image. + */ + public InfoOverlay(String imageUrl, String text) { + this(new ImageView(imageUrl), text); + } + + /** + * Constructs an InfoOverlay instance using the given Node (which can be + * an arbitrarily complex node / scenegraph, or a simple ImageView, for example), + * and places the given text string over top of it. + * + * @param content The arbitrarily complex scenegraph over which the text will be displayed. + * @param text The text to display over top of the node. + */ + public InfoOverlay(Node content, String text) { + getStyleClass().setAll(DEFAULT_STYLE_CLASS); + + setContent(content); + setText(text); + } + + + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new InfoOverlaySkin(this); + } + + + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + // --- content + private ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content"); //$NON-NLS-1$ + + /** + * + * @return an {@link ObjectProperty} containing the arbitrarily complex + * scenegraph over which the text will be displayed. + */ + public final ObjectProperty<Node> contentProperty() { + return content; + } + + /** + * Sets a new value for the {@link #contentProperty() }. + * @param content + */ + public final void setContent(Node content) { + contentProperty().set(content); + } + + /** + * + * @return the arbitrarily complex scenegraph over which the text will be + * displayed. + */ + public final Node getContent() { + return contentProperty().get(); + } + + + // --- text + + private StringProperty text = new SimpleStringProperty(this, "text"); //$NON-NLS-1$ + + /** + * @return A {@link StringProperty} representing the text displayed over top + * of the {@link #contentProperty() content}. + */ + public final StringProperty textProperty() { + return text; + } + + /** + * + * @return The text displayed over top of the {@link #contentProperty() content}. + */ + public final String getText() { + return textProperty().get(); + } + + /** + * Specifies the text to display over top of the {@link #contentProperty() content}. + * @param text + */ + public final void setText(String text) { + textProperty().set(text); + } + + + // --- showOnHover + private BooleanProperty showOnHover = new SimpleBooleanProperty(this, "showOnHover", true); //$NON-NLS-1$ + + /** + * + * @return A {@link BooleanProperty} representing whether the overlay on + * hover of the content node is showing. + */ + public final BooleanProperty showOnHoverProperty() { + return showOnHover; + } + + /** + * + * @return whether the overlay on hover of the content node is showing. + */ + public final boolean isShowOnHover() { + return showOnHoverProperty().get(); + } + + /** + * Specifies whether to show the overlay on hover of the content node (and + * to hide it again when the content is no longer being hovered). By default + * this is true. + * @param value + */ + public final void setShowOnHover(boolean value) { + showOnHoverProperty().set(value); + } + + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "info-overlay"; //$NON-NLS-1$ + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(InfoOverlay.class, "info-overlay.css"); + } +} diff --git a/src/org/controlsfx/control/ListSelectionView.java b/src/org/controlsfx/control/ListSelectionView.java new file mode 100644 index 0000000000000000000000000000000000000000..6c6d669d5ad44fb7901b4dda44c626a6eacc7ac2 --- /dev/null +++ b/src/org/controlsfx/control/ListSelectionView.java @@ -0,0 +1,370 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; + +import impl.org.controlsfx.skin.ListSelectionViewSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.Cell; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Skin; +import javafx.util.Callback; + +/** + * A control used to perform a multi-selection via the help of two list views. + * Items can be moved from one list (source) to the other (target). This can be + * done by either double clicking on the list items or by using one of the + * "move" buttons between the two lists. Each list can be decorated with a + * header and a footer node. The default header nodes are simply two labels + * ("Available", "Selected"). + * + * <h3>Screenshot</h3> + * + * <center><img src="list-selection-view.png" alt="Screenshot of ListSelectionView"></center> + * + * <h3>Code Example</h3> + * + * <pre> + * ListSelectionView<String> view = new ListSelectionView<>(); + * view.getSourceItems().add("One", "Two", "Three"); + * view.getTargetItems().add("Four", "Five"); + * </pre> + * + * @param <T> + * the type of the list items + */ +public class ListSelectionView<T> extends ControlsFXControl { + + private static final String DEFAULT_STYLECLASS = "list-selection-view"; + + /** + * Constructs a new dual list view. + */ + public ListSelectionView() { + getStyleClass().add(DEFAULT_STYLECLASS); + + Label sourceHeader = new Label( + localize(asKey("listSelectionView.header.source"))); + sourceHeader.getStyleClass().add("list-header-label"); + sourceHeader.setId("source-header-label"); + setSourceHeader(sourceHeader); + + Label targetHeader = new Label( + localize(asKey("listSelectionView.header.target"))); + targetHeader.getStyleClass().add("list-header-label"); + targetHeader.setId("target-header-label"); + setTargetHeader(targetHeader); + } + + @Override + protected Skin<ListSelectionView<T>> createDefaultSkin() { + return new ListSelectionViewSkin<>(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(ListSelectionView.class, "listselectionview.css"); + } + + private final ObjectProperty<Node> sourceHeader = new SimpleObjectProperty<>( + this, "sourceHeader"); + + /** + * A property used to store a reference to a node that will be displayed + * above the source list view. The default node is a {@link Label} + * displaying the text "Available". + * + * @return the property used to store the source header node + */ + public final ObjectProperty<Node> sourceHeaderProperty() { + return sourceHeader; + } + + /** + * Returns the value of {@link #sourceHeaderProperty()}. + * + * @return the source header node + */ + public final Node getSourceHeader() { + return sourceHeader.get(); + } + + /** + * Sets the value of {@link #sourceHeaderProperty()}. + * + * @param node + * the new header node to use for the source list + */ + public final void setSourceHeader(Node node) { + sourceHeader.set(node); + } + + private final ObjectProperty<Node> sourceFooter = new SimpleObjectProperty<>( + this, "sourceFooter"); + + /** + * A property used to store a reference to a node that will be displayed + * below the source list view. The default node is a node with two buttons + * for easily selecting / deselecting all elements in the list view. + * + * @return the property used to store the source footer node + */ + public final ObjectProperty<Node> sourceFooterProperty() { + return sourceFooter; + } + + /** + * Returns the value of {@link #sourceFooterProperty()}. + * + * @return the source footer node + */ + public final Node getSourceFooter() { + return sourceFooter.get(); + } + + /** + * Sets the value of {@link #sourceFooterProperty()}. + * + * @param node + * the new node shown below the source list + */ + public final void setSourceFooter(Node node) { + sourceFooter.set(node); + } + + private final ObjectProperty<Node> targetHeader = new SimpleObjectProperty<>( + this, "targetHeader"); + + /** + * A property used to store a reference to a node that will be displayed + * above the target list view. The default node is a {@link Label} + * displaying the text "Selected". + * + * @return the property used to store the target header node + */ + public final ObjectProperty<Node> targetHeaderProperty() { + return targetHeader; + } + + /** + * Returns the value of {@link #targetHeaderProperty()}. + * + * @return the source header node + */ + public final Node getTargetHeader() { + return targetHeader.get(); + } + + /** + * Sets the value of {@link #targetHeaderProperty()}. + * + * @param node + * the new node shown above the target list + */ + public final void setTargetHeader(Node node) { + targetHeader.set(node); + } + + private final ObjectProperty<Node> targetFooter = new SimpleObjectProperty<>( + this, "targetFooter"); + + /** + * A property used to store a reference to a node that will be displayed + * below the target list view. The default node is a node with two buttons + * for easily selecting / deselecting all elements in the list view. + * + * @return the property used to store the source footer node + */ + public final ObjectProperty<Node> targetFooterProperty() { + return targetFooter; + } + + /** + * Returns the value of {@link #targetFooterProperty()}. + * + * @return the source header node + */ + public final Node getTargetFooter() { + return targetFooter.get(); + } + + /** + * Sets the value of {@link #targetFooterProperty()}. + * + * @param node + * the new node shown below the target list + */ + public final void setTargetFooter(Node node) { + targetFooter.set(node); + } + + private ObjectProperty<ObservableList<T>> sourceItems; + + /** + * Sets the underlying data model for the ListView. Note that it has a + * generic type that must match the type of the ListView itself. + */ + public final void setSourceItems(ObservableList<T> value) { + sourceItemsProperty().set(value); + } + + /** + * Returns an {@link ObservableList} that contains the items currently being + * shown to the user in the source list. This may be null if + * {@link #setSourceItems(javafx.collections.ObservableList)} has previously + * been called, however, by default it is an empty ObservableList. + * + * @return An ObservableList containing the items to be shown to the user in + * the source list, or null if the items have previously been set to + * null. + */ + public final ObservableList<T> getSourceItems() { + return sourceItemsProperty().get(); + } + + /** + * The underlying data model for the source list view. Note that it has a + * generic type that must match the type of the source list view itself. + */ + public final ObjectProperty<ObservableList<T>> sourceItemsProperty() { + if (sourceItems == null) { + sourceItems = new SimpleObjectProperty<>(this, "sourceItems", + FXCollections.observableArrayList()); + } + return sourceItems; + } + + private ObjectProperty<ObservableList<T>> targetItems; + + /** + * Sets the underlying data model for the ListView. Note that it has a + * generic type that must match the type of the ListView itself. + */ + public final void setTargetItems(ObservableList<T> value) { + targetItemsProperty().set(value); + } + + /** + * Returns an {@link ObservableList} that contains the items currently being + * shown to the user in the target list. This may be null if + * {@link #setTargetItems(javafx.collections.ObservableList)} has previously + * been called, however, by default it is an empty ObservableList. + * + * @return An ObservableList containing the items to be shown to the user in + * the target list, or null if the items have previously been set to + * null. + */ + public final ObservableList<T> getTargetItems() { + return targetItemsProperty().get(); + } + + /** + * The underlying data model for the target list view. Note that it has a + * generic type that must match the type of the source list view itself. + */ + public final ObjectProperty<ObservableList<T>> targetItemsProperty() { + if (targetItems == null) { + targetItems = new SimpleObjectProperty<>(this, "targetItems", + FXCollections.observableArrayList()); + } + return targetItems; + } + + // --- Orientation + private final ObjectProperty<Orientation> orientation = new SimpleObjectProperty<>( + this, "orientation", Orientation.HORIZONTAL); //$NON-NLS-1$; + + /** + * The {@link Orientation} of the {@code ListSelectionView} - this can + * either be horizontal or vertical. + */ + public final ObjectProperty<Orientation> orientationProperty() { + return orientation; + } + + /** + * Sets the {@link Orientation} of the {@code ListSelectionView} - this can + * either be horizontal or vertical. + */ + public final void setOrientation(Orientation value) { + orientationProperty().set(value); + }; + + /** + * Returns the {@link Orientation} of the {@code ListSelectionView} - this + * can either be horizontal or vertical. + */ + public final Orientation getOrientation() { + return orientation.get(); + } + + // --- Cell Factory + private ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactory; + + /** + * Sets a new cell factory to use by both list views. This forces all old + * {@link ListCell}'s to be thrown away, and new ListCell's created with the + * new cell factory. + */ + public final void setCellFactory(Callback<ListView<T>, ListCell<T>> value) { + cellFactoryProperty().set(value); + } + + /** + * Returns the current cell factory. + */ + public final Callback<ListView<T>, ListCell<T>> getCellFactory() { + return cellFactory == null ? null : cellFactory.get(); + } + + /** + * <p> + * Setting a custom cell factory has the effect of deferring all cell + * creation, allowing for total customization of the cell. Internally, the + * ListView is responsible for reusing ListCells - all that is necessary is + * for the custom cell factory to return from this function a ListCell which + * might be usable for representing any item in the ListView. + * + * <p> + * Refer to the {@link Cell} class documentation for more detail. + */ + public final ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactoryProperty() { + if (cellFactory == null) { + cellFactory = new SimpleObjectProperty<>(this, "cellFactory"); + } + return cellFactory; + } +} diff --git a/src/org/controlsfx/control/MaskerPane.java b/src/org/controlsfx/control/MaskerPane.java new file mode 100644 index 0000000000000000000000000000000000000000..0003d4669a31487e236970977e7652dd402d0c7b --- /dev/null +++ b/src/org/controlsfx/control/MaskerPane.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.MaskerPaneSkin; +import javafx.beans.property.*; +import javafx.scene.Node; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.Skin; +import javafx.scene.layout.StackPane; + + +/** + * <p>MaskerPane is designed to be placed alongside other controls in a {@link StackPane}, + * in order to visually mask these controls, preventing them from being accessed + * for a short period of time. This comes in handy whenever waiting on asynchronous + * code to finish, and you do not want the user to be able to modify the state + * of the UI while waiting.</p> + * + * <p>To use this control, it is necessary to place it as the last child in a {@link StackPane}, + * with the other children being masked by this MaskerPane when visible. Simply use + * {@link #setVisible(boolean)} to toggle between visible states.</p> + */ +public class MaskerPane extends ControlsFXControl { + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Construct a new {@link MaskerPane} + */ + public MaskerPane() { getStyleClass().add("masker-pane"); } //$NON-NLS-1$ + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // -- Background Color + + // -- Progress + private final DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1.0); //$NON-NLS-1$ + public final DoubleProperty progressProperty() { return progress; } + public final double getProgress() { return progress.get(); } + public final void setProgress(double progress) { this.progress.set(progress); } + + // -- Progress Node + private final ObjectProperty<Node> progressNode = new SimpleObjectProperty<Node>() { + { + ProgressIndicator node = new ProgressIndicator(); + node.progressProperty().bind(progress); + setValue(node); + } + + @Override public String getName() { return "progressNode"; } //$NON-NLS-1$ + @Override public Object getBean() { return MaskerPane.this; } + }; + public final ObjectProperty<Node> progressNodeProperty() { return progressNode; } + public final Node getProgressNode() { return progressNode.get();} + public final void setProgressNode(Node progressNode) { this.progressNode.set(progressNode); } + + // -- Progress Visibility + private final BooleanProperty progressVisible = new SimpleBooleanProperty(this, "progressVisible", true); //$NON-NLS-1$ + public final BooleanProperty progressVisibleProperty() { return progressVisible; } + public final boolean getProgressVisible() { return progressVisible.get(); } + public final void setProgressVisible(boolean progressVisible) { this.progressVisible.set(progressVisible); } + + // -- Text + private final StringProperty text = new SimpleStringProperty(this, "text", "Please Wait..."); //$NON-NLS-1$ + public final StringProperty textProperty() { return text; } + public final String getText() { return text.get(); } + public final void setText(String text) { this.text.set(text); } + + + + /************************************************************************** + * + * Interface implementation + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { return new MaskerPaneSkin(this); } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { return getUserAgentStylesheet(MaskerPane.class, "maskerpane.css"); } //$NON-NLS-1$ +} \ No newline at end of file diff --git a/src/org/controlsfx/control/MasterDetailPane.java b/src/org/controlsfx/control/MasterDetailPane.java new file mode 100644 index 0000000000000000000000000000000000000000..18f106112aaec20778c8feb260446cc456412492 --- /dev/null +++ b/src/org/controlsfx/control/MasterDetailPane.java @@ -0,0 +1,386 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.MasterDetailPaneSkin; + +import java.util.Objects; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Pos; +import javafx.geometry.Side; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.Skin; + +/** + * A master / detail pane is used to display two nodes with a strong + * relationship to each other. Most of the time the user works with the + * information displayed in the master node but every once in a while additional + * information is required and can be made visible via the detail node. By + * default the detail appears with a short slide-in animation and disappears + * with a slide-out. This control allows the detail node to be positioned in + * four different locations (top, bottom, left, or right). + * <h3>Screenshot</h3> + * To better describe what a master / detail pane is, please refer to the picture + * below: + * <center><img src="masterDetailPane.png" alt="Screenshot of MasterDetailPane"></center> + * <h3>Code Sample</h3> + * <pre> + * {@code + * MasterDetailPane pane = new MasterDetailPane(); + * pane.setMasterNode(new TableView()); + * pane.setDetailNode(new PropertySheet()); + * pane.setDetailSide(Side.RIGHT); + * pane.setShowDetailNode(true); + * }</pre> + */ +public class MasterDetailPane extends ControlsFXControl { + + /** + * Constructs a new pane. + * + * @param side + * the position where the detail will be shown (top, bottom, + * left, right) + * @param masterNode + * the master node (always visible) + * @param detailNode + * the detail node (slides in and out) + * @param showDetail + * the initial state of the detail node (shown or hidden) + */ + public MasterDetailPane(Side side, Node masterNode, Node detailNode, + boolean showDetail) { + + super(); + + Objects.requireNonNull(side); + Objects.requireNonNull(masterNode); + Objects.requireNonNull(detailNode); + + getStyleClass().add("master-detail-pane"); //$NON-NLS-1$ + + setDetailSide(side); + setMasterNode(masterNode); + setDetailNode(detailNode); + setShowDetailNode(showDetail); + + switch (side) { + case BOTTOM: + case RIGHT: + setDividerPosition(.8); + break; + case TOP: + case LEFT: + setDividerPosition(.2); + break; + default: + break; + + } + } + + /** + * Constructs a new pane with two placeholder nodes. + * + * @param pos + * the position where the details will be shown (top, bottom, + * left, right) + * @param showDetail + * the initial state of the detail node (shown or hidden) + */ + public MasterDetailPane(Side pos, boolean showDetail) { + this(pos, new Placeholder(true), new Placeholder(false), showDetail); + } + + /** + * Constructs a new pane with two placeholder nodes. The detail node will be + * shown. + * + * @param pos + * the position where the details will be shown (top, bottom, + * left, right) + */ + public MasterDetailPane(Side pos) { + this(pos, new Placeholder(true), new Placeholder(false), true); + } + + /** + * Constructs a new pane with two placeholder nodes. The detail node will be + * shown and to the right of the master node. + */ + public MasterDetailPane() { + this(Side.RIGHT, new Placeholder(true), new Placeholder(false), true); + } + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new MasterDetailPaneSkin(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(MasterDetailPane.class, "masterdetailpane.css"); + } + + // Detail postion support + + private final ObjectProperty<Side> detailSide = new SimpleObjectProperty<>( + this, "detailSide", Side.RIGHT); //$NON-NLS-1$ + + /** + * The property used to store the side where the detail node will be shown. + * + * @return the details side property + */ + public final ObjectProperty<Side> detailSideProperty() { + return detailSide; + } + + /** + * Returns the value of the detail side property. + * + * @return the side where the detail node will be shown (left, right, top, + * bottom) + */ + public final Side getDetailSide() { + return detailSideProperty().get(); + } + + /** + * Sets the value of the detail side property. + * + * @param side + * the side where the detail node will be shown (left, right, + * top, bottom) + */ + public final void setDetailSide(Side side) { + Objects.requireNonNull(side); + detailSideProperty().set(side); + } + + // Show / hide detail node support. + + private final BooleanProperty showDetailNode = new SimpleBooleanProperty( + this, "showDetailNode", true); //$NON-NLS-1$ + + /** + * The property used to store the visibility of the detail node. + * + * @return true if the pane is currently expanded (shows the detail node) + */ + public final BooleanProperty showDetailNodeProperty() { + return showDetailNode; + } + + /** + * Returns the value of the "show detail node" property. + * + * @return true if the pane is currently expanded (shows the detail node) + */ + public final boolean isShowDetailNode() { + return showDetailNodeProperty().get(); + } + + /** + * Sets the value of the "show detail node" property. + * + * @param show + * if true the pane will show the detail node + */ + public final void setShowDetailNode(boolean show) { + showDetailNodeProperty().set(show); + } + + // Master node support. + + private final ObjectProperty<Node> masterNode = new SimpleObjectProperty<>( + this, "masterNode"); //$NON-NLS-1$ + + /** + * The property used to store the master node. + * + * @return the master node property + */ + public final ObjectProperty<Node> masterNodeProperty() { + return masterNode; + } + + /** + * Returns the value of the master node property. + * + * @return the master node + */ + public final Node getMasterNode() { + return masterNodeProperty().get(); + } + + /** + * Sets the value of the master node property. + * + * @param node + * the new master node + */ + public final void setMasterNode(Node node) { + Objects.requireNonNull(node); + masterNodeProperty().set(node); + } + + // Detail node support. + + private final ObjectProperty<Node> detailNode = new SimpleObjectProperty<>( + this, "detailNode"); //$NON-NLS-1$ + + /** + * The property used to store the detail node. + * + * @return the detail node property + */ + public final ObjectProperty<Node> detailNodeProperty() { + return detailNode; + } + + /** + * Returns the value of the detail node property. + * + * @return the detail node + */ + public final Node getDetailNode() { + return detailNodeProperty().get(); + } + + /** + * Sets the value of the detail node property. + * + * @param node + * the new master node + */ + public final void setDetailNode(Node node) { + Objects.requireNonNull(node); + detailNodeProperty().set(node); + } + + // Animation support. + + private final BooleanProperty animated = new SimpleBooleanProperty(this, + "animated", true); //$NON-NLS-1$ + + /** + * The property used to store the "animated" flag. If true then the detail + * node will be shown / hidden with a short slide in / out animation. + * + * @return the "animated" property + */ + public final BooleanProperty animatedProperty() { + return animated; + } + + /** + * Returns the value of the "animated" property. + * + * @return true if the detail node will be shown with a short animation + * (slide in) + */ + public final boolean isAnimated() { + return animatedProperty().get(); + } + + /** + * Sets the value of the "animated" property. + * + * @param animated + * if true the detail node will be shown with a short animation + * (slide in) + */ + public final void setAnimated(boolean animated) { + animatedProperty().set(animated); + } + + private DoubleProperty dividerPosition = new SimpleDoubleProperty(this, + "dividerPosition", .33); //$NON-NLS-1$ + + /** + * Stores the location of the divider. + * + * @return the divider location + */ + public final DoubleProperty dividerPositionProperty() { + return dividerPosition; + } + + /** + * Returns the value of the divider position property. + * + * @return the position of the divider + */ + public final double getDividerPosition() { + return dividerPosition.get(); + } + + /** + * Sets the value of the divider position property. + * + * @param position + * the new divider position. + */ + public final void setDividerPosition(double position) { + /** + * See https://bitbucket.org/controlsfx/controlsfx/issue/145/divider-position-in-masterdetailpane-is + * + * Thie work-around is not the best ever found but at least it works. + */ + if(getDividerPosition() == position){ + dividerPosition.set(-1); + } + dividerPosition.set(position); + } + + /* + * A placeholder for the constructors that do not accept a master or a + * detail node. + */ + private static final class Placeholder extends Label { + + public Placeholder(boolean master) { + super(master ? "Master" : "Detail"); //$NON-NLS-1$ //$NON-NLS-2$ + + setAlignment(Pos.CENTER); + + if (master) { + setStyle("-fx-background-color: -fx-background;"); //$NON-NLS-1$ + } else { + setStyle("-fx-background-color: derive(-fx-background, -10%);"); //$NON-NLS-1$ + } + } + } +} diff --git a/src/org/controlsfx/control/NotificationPane.java b/src/org/controlsfx/control/NotificationPane.java new file mode 100644 index 0000000000000000000000000000000000000000..4bb82b9e55a687c76435a1c6326c07722149b279 --- /dev/null +++ b/src/org/controlsfx/control/NotificationPane.java @@ -0,0 +1,630 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.NotificationPaneSkin; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.scene.web.WebView; + +import org.controlsfx.control.action.Action; + +/** + * The NotificationPane control is a container control that, when prompted by + * the {@link #show()} method, will show a non-modal message to the user. The + * notification appears as a bar that will slide in to their application window, + * either from the top or the bottom of the NotificationPane (based on + * {@link #showFromTopProperty()}) wherever that may be in the scenegraph. + * + * <h3>Alternative Styling</h3> + * <p>As is visible in the screenshots further down this documentation, + * there are two different styles supported by the NotificationPane control. + * Firstly, there is the default style based on the JavaFX Modena look. The + * alternative style is what is currently referred to as the 'dark' look. To + * enable this functionality, simply do the following: + * + * <pre> + * {@code + * NotificationPane notificationPane = new NotificationPane(); + * notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK); + * }</pre> + * + * <h3>Screenshots</h3> + * <p>To better explain NotificationPane, here is a table showing both the + * default and dark look for the NotificationPane on a Windows machine (although + * that shouldn't impact the visuals a great deal). Also, to show the difference + * between top and bottom placement, these two modes are also captured in the + * screenshots below: + * + * <br> + * <center> + * <table style="border: 1px solid gray; max-width:750px" summary="NotificationPane Screenshots"> + * <tr> + * <th width="200"><center><h3>Setting</h3></center></th> + * <th width="520"><center><h3>Screenshot</h3></center></th> + * </tr> + * <tr> + * <td valign="top" style="text-align:center;"><strong>Light theme from top:</strong></td> + * <td><center><img src="notication-pane-light-top.png" alt="Screenshot of NotificationPane - Light theme from top"></center></td> + * </tr> + * <tr> + * <td valign="top" style="text-align:center;"><strong>Light theme from bottom:</strong></td> + * <td><center><img src="notication-pane-light-bottom.png" alt="Screenshot of NotificationPane - Light theme from bottom"></center></td> + * </tr> + * <tr> + * <td valign="top" style="text-align:center;"><strong>Dark theme from top:</strong></td> + * <td><center><img src="notication-pane-dark-top.png" alt="Screenshot of NotificationPane - Dark theme from top"></center></td> + * </tr> + * <tr> + * <td valign="top" style="text-align:center;"><strong>Dark theme from bottom:</strong></td> + * <td><center><img src="notication-pane-dark-bottom.png" alt="Screenshot of NotificationPane - Dark theme from bottom"></center></td> + * </tr> + * </table> + * </center> + * + * <h3>Code Examples</h3> + * + * <p>NotificationPane is a conceptually very simple control - you simply create + * your user interface as you normally would, and then wrap it inside the + * NotificationPane. You can then show a notification bar on top of your content + * simply by calling {@link #show()} on the notification bar. Here is an example: + * + * <pre> + * {@code + * // Create a WebView + * WebView webView = new WebView(); + * + * // Wrap it inside a NotificationPane + * NotificationPane notificationPane = new NotificationPane(webView); + * + * // and put the NotificationPane inside a Tab + * Tab tab1 = new Tab("Tab 1"); + * tab1.setContent(notificationPane); + * + * // and the Tab inside a TabPane. We just have one tab here, but of course + * // you can have more! + * TabPane tabPane = new TabPane(); + * tabPane.getTabs().addAll(tab1); + * }</pre> + * + * <p>Now that the notification pane is installed inside the tab, at some point + * later in you application lifecycle, you can do something like the following + * to have the notification bar slide into view: + * + * <pre> + * {@code + * notificationPane.setText("Do you want to save your password?"); + * notificationPane.getActions().add(new AbstractAction("Save Password") { + * public void execute(ActionEvent ae) { + * // do save... + * + * // then hide... + * notificationPane.hide(); + * } + * }}</pre> + * + * @see Action + */ +public class NotificationPane extends ControlsFXControl { + + /*************************************************************************** + * + * Static fields + * + **************************************************************************/ + + public static final String STYLE_CLASS_DARK = "dark"; //$NON-NLS-1$ + + /** + * Called when the NotificationPane <b>will</b> be shown. + */ + public static final EventType<Event> ON_SHOWING = + new EventType<>(Event.ANY, "NOTIFICATION_PANE_ON_SHOWING"); //$NON-NLS-1$ + + /** + * Called when the NotificationPane shows. + */ + public static final EventType<Event> ON_SHOWN = + new EventType<>(Event.ANY, "NOTIFICATION_PANE_ON_SHOWN"); //$NON-NLS-1$ + + /** + * Called when the NotificationPane <b>will</b> be hidden. + */ + public static final EventType<Event> ON_HIDING = + new EventType<>(Event.ANY, "NOTIFICATION_PANE_ON_HIDING"); //$NON-NLS-1$ + + /** + * Called when the NotificationPane is hidden. + */ + public static final EventType<Event> ON_HIDDEN = + new EventType<>(Event.ANY, "NOTIFICATION_PANE_ON_HIDDEN"); //$NON-NLS-1$ + + + + /*************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates an instance of NotificationPane with no + * {@link #contentProperty() content}, {@link #textProperty() text}, + * {@link #graphicProperty() graphic} properties set, and no + * {@link #getActions() actions} specified. + */ + public NotificationPane() { + this(null); + } + + /** + * Creates an instance of NotificationPane with the + * {@link #contentProperty() content} property set, but no + * {@link #textProperty() text} or + * {@link #graphicProperty() graphic} property set, and no + * {@link #getActions() actions} specified. + * + * @param content The content to show in the NotificationPane behind where + * the notification bar will appear, that is, the content + * <strong>will not</strong>appear in the notification bar. + */ + public NotificationPane(Node content) { + getStyleClass().add(DEFAULT_STYLE_CLASS); + setContent(content); + + updateStyleClasses(); + } + + + + /*************************************************************************** + * + * Overriding public API + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new NotificationPaneSkin(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(NotificationPane.class, "notificationpane.css"); + } + + /*************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- content + private ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content"); //$NON-NLS-1$ + + /** + * The content property represents what is shown in the scene + * <strong>that is not within</strong> the notification bar. In other words, + * it is what the notification bar should appear on top of. For example, in + * the scenario where you are using a {@link WebView} to show to the user + * websites, and you want to popup up a notification bar to save a password, + * the content would be the {@link WebView}. Refer to the + * {@link NotificationPane} class documentation for more details. + * + * @return A property representing the content of this NotificationPane. + */ + public final ObjectProperty<Node> contentProperty() { + return content; + } + + /** + * Set the content to be shown in the scene, + * <strong>that is not within</strong> the notification bar. + * @param value + */ + public final void setContent(Node value) { + this.content.set(value); + } + + /** + * + * @return The content shown in the scene. + */ + public final Node getContent() { + return content.get(); + } + + + // --- text + private StringProperty text = new SimpleStringProperty(this, "text"); //$NON-NLS-1$ + + /** + * The text property represents the text to show within the popup + * notification bar that appears on top of the + * {@link #contentProperty() content} that is within the NotificationPane. + * + * @return A property representing the text shown in the notification bar. + */ + public final StringProperty textProperty() { + return text; + } + + /** + * Sets the text to show within the popup + * notification bar that appears on top of the + * {@link #contentProperty() content}. + * @param value + */ + public final void setText(String value) { + this.text.set(value); + } + + /** + * + * @return the text showing within the popup + * notification bar that appears on top of the + * {@link #contentProperty() content}. + */ + public final String getText() { + return text.get(); + } + + + // --- graphic + private ObjectProperty<Node> graphic = new SimpleObjectProperty<>(this, "graphic"); //$NON-NLS-1$ + + /** + * The graphic property represents the {@link Node} to show within the popup + * notification bar that appears on top of the + * {@link #contentProperty() content} that is within the NotificationPane. + * Despite the term 'graphic', this can be an arbitrarily complex scenegraph + * in its own right. + * + * @return A property representing the graphic shown in the notification bar. + */ + public final ObjectProperty<Node> graphicProperty() { + return graphic; + } + + /** + * Sets the {@link Node} to show within the popup + * notification bar. + * @param value + */ + public final void setGraphic(Node value) { + this.graphic.set(value); + } + + /** + * + * @return the {@link Node} to show within the popup + * notification bar. + */ + public final Node getGraphic() { + return graphic.get(); + } + + + // --- showing + private ReadOnlyBooleanWrapper showing = new ReadOnlyBooleanWrapper(this, "showing"); //$NON-NLS-1$ + + /** + * A read-only property that represents whether the notification bar popup + * should be showing to the user or not. To toggle visibility, use the + * {@link #show()} and {@link #hide()} methods. + * + * @return A property representing whether the notification bar is currently showing. + */ + public final ReadOnlyBooleanProperty showingProperty() { + return showing.getReadOnlyProperty(); + } + private final void setShowing(boolean value) { + this.showing.set(value); + } + /** + * + * @return whether the notification bar is currently showing. + */ + public final boolean isShowing() { + return showing.get(); + } + + + // --- show from top + private BooleanProperty showFromTop = new SimpleBooleanProperty(this, "showFromTop", true) { //$NON-NLS-1$ + @Override protected void invalidated() { + updateStyleClasses(); + } + }; + + /** + * A property representing whether the notification bar should appear from the + * top or the bottom of the NotificationPane area. By default it will appear + * from the top, but this can be changed by setting this property to false. + * + * @return A property representing where the notification bar should appear from. + */ + public final BooleanProperty showFromTopProperty() { + return showFromTop; + } + + /** + * Sets whether the notification bar should appear from the + * top or the bottom of the NotificationPane area. + * @param value + */ + public final void setShowFromTop(boolean value) { + this.showFromTop.set(value); + } + + /** + * @return whether the notification bar is appearing from the + * top or the bottom of the NotificationPane area. + */ + public final boolean isShowFromTop() { + return showFromTop.get(); + } + + + // --- On Showing + public final ObjectProperty<EventHandler<Event>> onShowingProperty() { return onShowing; } + /** + * Called just prior to the {@code NotificationPane} being shown. + */ + public final void setOnShowing(EventHandler<Event> value) { onShowingProperty().set(value); } + public final EventHandler<Event> getOnShowing() { return onShowingProperty().get(); } + private ObjectProperty<EventHandler<Event>> onShowing = new SimpleObjectProperty<EventHandler<Event>>(this, "onShowing") { //$NON-NLS-1$ + @Override protected void invalidated() { + setEventHandler(ON_SHOWING, get()); + } + }; + + + // -- On Shown + public final ObjectProperty<EventHandler<Event>> onShownProperty() { return onShown; } + /** + * Called just after the {@link NotificationPane} is shown. + */ + public final void setOnShown(EventHandler<Event> value) { onShownProperty().set(value); } + public final EventHandler<Event> getOnShown() { return onShownProperty().get(); } + private ObjectProperty<EventHandler<Event>> onShown = new SimpleObjectProperty<EventHandler<Event>>(this, "onShown") { //$NON-NLS-1$ + @Override protected void invalidated() { + setEventHandler(ON_SHOWN, get()); + } + }; + + + // --- On Hiding + public final ObjectProperty<EventHandler<Event>> onHidingProperty() { return onHiding; } + /** + * Called just prior to the {@link NotificationPane} being hidden. + */ + public final void setOnHiding(EventHandler<Event> value) { onHidingProperty().set(value); } + public final EventHandler<Event> getOnHiding() { return onHidingProperty().get(); } + private ObjectProperty<EventHandler<Event>> onHiding = new SimpleObjectProperty<EventHandler<Event>>(this, "onHiding") { //$NON-NLS-1$ + @Override protected void invalidated() { + setEventHandler(ON_HIDING, get()); + } + }; + + + // --- On Hidden + public final ObjectProperty<EventHandler<Event>> onHiddenProperty() { return onHidden; } + /** + * Called just after the {@link NotificationPane} has been hidden. + */ + public final void setOnHidden(EventHandler<Event> value) { onHiddenProperty().set(value); } + public final EventHandler<Event> getOnHidden() { return onHiddenProperty().get(); } + private ObjectProperty<EventHandler<Event>> onHidden = new SimpleObjectProperty<EventHandler<Event>>(this, "onHidden") { //$NON-NLS-1$ + @Override protected void invalidated() { + setEventHandler(ON_HIDDEN, get()); + } + }; + + // --- close button visibility + private BooleanProperty closeButtonVisible = new SimpleBooleanProperty(this, "closeButtonVisible", true); //$NON-NLS-1$ + + /** + * A property representing whether the close button in the {@code NotificationPane} should be visible or not. + * By default it will appear but this can be changed by setting this property to false. + * + * @return A property representing whether the close button in the {@code NotificationPane} should be visible. + */ + public final BooleanProperty closeButtonVisibleProperty() { + return closeButtonVisible; + } + + /** + * Sets whether the close button in {@code NotificationPane} should be visible. + * + * @param value + */ + public final void setCloseButtonVisible(boolean value) { + this.closeButtonVisible.set(value); + } + + /** + * @return whether the close button in {@code NotificationPane} is visible. + */ + public final boolean isCloseButtonVisible() { + return closeButtonVisible.get(); + } + + /*************************************************************************** + * + * Public API + * + **************************************************************************/ + + // --- actions + private final ObservableList<Action> actions = FXCollections.<Action> observableArrayList(); + + /** + * Observable list of actions used for the actions area of the notification + * bar. Modifying the contents of this list will change the actions available to + * the user. + * @return The {@link ObservableList} of actions available to the user. + */ + public final ObservableList<Action> getActions() { + return actions; + } + + /** + * Call this to make the notification bar appear on top of the + * {@link #contentProperty() content} of this {@link NotificationPane}. + * If the notification bar is already showing this will be a no-op. + */ + public void show() { + setShowing(true); + } + + /** + * Shows the NotificationPane with the + * {@link #contentProperty() content} and {@link #textProperty() text} + * property set, but no {@link #graphicProperty() graphic} property set, and + * no {@link #getActions() actions} specified. + * + * @param text The text to show in the notification pane. + */ + public void show(final String text) { + hideAndThen(new Runnable() { + @Override public void run() { + setText(text); + setShowing(true); + } + }); + } + + /** + * Shows the NotificationPane with the + * {@link #contentProperty() content}, {@link #textProperty() text} and + * {@link #graphicProperty() graphic} properties set, but no + * {@link #getActions() actions} specified. + * + * @param text The text to show in the notification pane. + * @param graphic The node to show in the notification pane. + */ + public void show(final String text, final Node graphic) { + hideAndThen(new Runnable() { + @Override public void run() { + setText(text); + setGraphic(graphic); + setShowing(true); + } + }); + } + + /** + * Shows the NotificationPane with the + * {@link #contentProperty() content}, {@link #textProperty() text} and + * {@link #graphicProperty() graphic} property set, and the provided actions + * copied into the {@link #getActions() actions} list. + * + * @param text The text to show in the notification pane. + * @param graphic The node to show in the notification pane. + * @param actions The actions to show in the notification pane. + */ + public void show(final String text, final Node graphic, final Action... actions) { + hideAndThen(new Runnable() { + @Override public void run() { + setText(text); + setGraphic(graphic); + + if (actions == null) { + getActions().clear(); + } else { + for (Action action : actions) { + if (action == null) continue; + getActions().add(action); + } + } + + setShowing(true); + } + }); + } + + /** + * Call this to make the notification bar disappear from the + * {@link #contentProperty() content} of this {@link NotificationPane}. + * If the notification bar is already hidden this will be a no-op. + */ + public void hide() { + setShowing(false); + } + + + + /************************************************************************** + * * + * Private Implementation * + * * + **************************************************************************/ + + private void updateStyleClasses() { + getStyleClass().removeAll("top", "bottom"); //$NON-NLS-1$ //$NON-NLS-2$ + getStyleClass().add(isShowFromTop() ? "top" : "bottom"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private void hideAndThen(final Runnable r) { + if (isShowing()) { + final EventHandler<Event> eventHandler = new EventHandler<Event>() { + @Override public void handle(Event e) { + r.run(); + removeEventHandler(NotificationPane.ON_HIDDEN, this); + } + }; + addEventHandler(NotificationPane.ON_HIDDEN, eventHandler); + hide(); + } else { + r.run(); + } + } + + + + /************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "notification-pane"; //$NON-NLS-1$ +} diff --git a/src/org/controlsfx/control/Notifications.java b/src/org/controlsfx/control/Notifications.java new file mode 100644 index 0000000000000000000000000000000000000000..f223db184d8fda10db02915e4a73ec58e2d759c9 --- /dev/null +++ b/src/org/controlsfx/control/Notifications.java @@ -0,0 +1,645 @@ +/** + * Copyright (c) 2014, 2016, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.NotificationBar; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.ParallelTransition; +import javafx.animation.Timeline; +import javafx.animation.Transition; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.geometry.Rectangle2D; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.image.ImageView; +import javafx.stage.Popup; +import javafx.stage.PopupWindow; +import javafx.stage.Screen; +import javafx.stage.Window; +import javafx.util.Duration; + +import org.controlsfx.control.action.Action; +import org.controlsfx.tools.Utils; + +/** + * An API to show popup notification messages to the user in the corner of their + * screen, unlike the {@link NotificationPane} which shows notification messages + * within your application itself. + * + * <h3>Screenshot</h3> + * <p> + * The following screenshot shows a sample notification rising from the + * bottom-right corner of my screen: + * + * <br> + * <br> + * <img src="notifications.png" alt="Screenshot of Notifications"> + * + * <h3>Code Example:</h3> + * <p> + * To create the notification shown in the screenshot, simply do the following: + * + * <pre> + * {@code + * Notifications.create() + * .title("Title Text") + * .text("Hello World 0!") + * .showWarning(); + * } + * </pre> + */ +public class Notifications { + + /*************************************************************************** + * * Static fields * * + **************************************************************************/ + + private static final String STYLE_CLASS_DARK = "dark"; //$NON-NLS-1$ + + /*************************************************************************** + * * Private fields * * + **************************************************************************/ + + private String title; + private String text; + private Node graphic; + private ObservableList<Action> actions = FXCollections.observableArrayList(); + private Pos position = Pos.BOTTOM_RIGHT; + private Duration hideAfterDuration = Duration.seconds(5); + private boolean hideCloseButton; + private EventHandler<ActionEvent> onAction; + private Window owner; + private Screen screen = Screen.getPrimary(); + + private List<String> styleClass = new ArrayList<>(); + + /*************************************************************************** + * * Constructors * * + **************************************************************************/ + + // we do not allow instantiation of the Notifications class directly - users + // must go via the builder API (that is, calling create()) + private Notifications() { + // no-op + } + + /*************************************************************************** + * * Public API * * + **************************************************************************/ + + /** + * Call this to begin the process of building a notification to show. + */ + public static Notifications create() { + return new Notifications(); + } + + /** + * Specify the text to show in the notification. + */ + public Notifications text(String text) { + this.text = text; + return this; + } + + /** + * Specify the title to show in the notification. + */ + public Notifications title(String title) { + this.title = title; + return this; + } + + /** + * Specify the graphic to show in the notification. + */ + public Notifications graphic(Node graphic) { + this.graphic = graphic; + return this; + } + + /** + * Specify the position of the notification on screen, by default it is + * {@link Pos#BOTTOM_RIGHT bottom-right}. + */ + public Notifications position(Pos position) { + this.position = position; + return this; + } + + /** + * The dialog window owner - which can be {@link Screen}, {@link Window} + * or {@link Node}. If specified, the notifications will be inside + * the owner, otherwise the notifications will be shown within the whole + * primary (default) screen. + */ + public Notifications owner(Object owner) { + if (owner instanceof Screen) { + this.screen = (Screen) owner; + } else { + this.owner = Utils.getWindow(owner); + } + return this; + } + + /** + * Specify the duration that the notification should show, after which it + * will be hidden. + */ + public Notifications hideAfter(Duration duration) { + this.hideAfterDuration = duration; + return this; + } + + /** + * Specify what to do when the user clicks on the notification (in addition + * to the notification hiding, which happens whenever the notification is + * clicked on). + */ + public Notifications onAction(EventHandler<ActionEvent> onAction) { + this.onAction = onAction; + return this; + } + + /** + * Specify that the notification should use the built-in dark styling, + * rather than the default 'modena' notification style (which is a + * light-gray). + */ + public Notifications darkStyle() { + styleClass.add(STYLE_CLASS_DARK); + return this; + } + + /** + * Specify that the close button in the top-right corner of the notification + * should not be shown. + */ + public Notifications hideCloseButton() { + this.hideCloseButton = true; + return this; + } + + /** + * Specify the actions that should be shown in the notification as buttons. + */ + public Notifications action(Action... actions) { + this.actions = actions == null ? FXCollections.<Action> observableArrayList() : FXCollections + .observableArrayList(actions); + return this; + } + + /** + * Instructs the notification to be shown, and that it should use the + * built-in 'warning' graphic. + */ + public void showWarning() { + graphic(new ImageView(Notifications.class.getResource("/org/controlsfx/dialog/dialog-warning.png").toExternalForm())); //$NON-NLS-1$ + show(); + } + + /** + * Instructs the notification to be shown, and that it should use the + * built-in 'information' graphic. + */ + public void showInformation() { + graphic(new ImageView(Notifications.class.getResource("/org/controlsfx/dialog/dialog-information.png").toExternalForm())); //$NON-NLS-1$ + show(); + } + + /** + * Instructs the notification to be shown, and that it should use the + * built-in 'error' graphic. + */ + public void showError() { + graphic(new ImageView(Notifications.class.getResource("/org/controlsfx/dialog/dialog-error.png").toExternalForm())); //$NON-NLS-1$ + show(); + } + + /** + * Instructs the notification to be shown, and that it should use the + * built-in 'confirm' graphic. + */ + public void showConfirm() { + graphic(new ImageView(Notifications.class.getResource("/org/controlsfx/dialog/dialog-confirm.png").toExternalForm())); //$NON-NLS-1$ + show(); + } + + /** + * Instructs the notification to be shown. + */ + public void show() { + NotificationPopupHandler.getInstance().show(this); + } + + /*************************************************************************** + * * Private support classes * * + **************************************************************************/ + + // not public so no need for JavaDoc + private static final class NotificationPopupHandler { + + private static final NotificationPopupHandler INSTANCE = new NotificationPopupHandler(); + + private double startX; + private double startY; + private double screenWidth; + private double screenHeight; + + static final NotificationPopupHandler getInstance() { + return INSTANCE; + } + + private final Map<Pos, List<Popup>> popupsMap = new HashMap<>(); + private final double padding = 15; + + // for animating in the notifications + private ParallelTransition parallelTransition = new ParallelTransition(); + + private boolean isShowing = false; + + public void show(Notifications notification) { + Window window; + if (notification.owner == null) { + /* + * If the owner is not set, we work with the whole screen. + */ + Rectangle2D screenBounds = notification.screen.getVisualBounds(); + startX = screenBounds.getMinX(); + startY = screenBounds.getMinY(); + screenWidth = screenBounds.getWidth(); + screenHeight = screenBounds.getHeight(); + + window = Utils.getWindow(null); + } else { + /* + * If the owner is set, we will make the notifications popup + * inside its window. + */ + startX = notification.owner.getX(); + startY = notification.owner.getY(); + screenWidth = notification.owner.getWidth(); + screenHeight = notification.owner.getHeight(); + window = notification.owner; + } + show(window, notification); + } + + private void show(Window owner, final Notifications notification) { + // Stylesheets which are added to the scene of a popup aren't + // considered for styling. For this reason, we need to find the next + // window in the hierarchy which isn't a popup. + Window ownerWindow = owner; + while (ownerWindow instanceof PopupWindow) { + ownerWindow = ((PopupWindow) ownerWindow).getOwnerWindow(); + } + // need to install our CSS + Scene ownerScene = ownerWindow.getScene(); + if (ownerScene != null) { + String stylesheetUrl = Notifications.class.getResource("notificationpopup.css").toExternalForm(); //$NON-NLS-1$ + if (!ownerScene.getStylesheets().contains(stylesheetUrl)) { + // The stylesheet needs to be added at the beginning so that + // the styling can be adjusted with custom stylesheets. + ownerScene.getStylesheets().add(0, stylesheetUrl); + } + } + + final Popup popup = new Popup(); + popup.setAutoFix(false); + + final Pos p = notification.position; + + final NotificationBar notificationBar = new NotificationBar() { + @Override public String getTitle() { + return notification.title; + } + + @Override public String getText() { + return notification.text; + } + + @Override public Node getGraphic() { + return notification.graphic; + } + + @Override public ObservableList<Action> getActions() { + return notification.actions; + } + + @Override public boolean isShowing() { + return isShowing; + } + + @Override protected double computeMinWidth(double height) { + String text = getText(); + Node graphic = getGraphic(); + if ((text == null || text.isEmpty()) && (graphic != null)) { + return graphic.minWidth(height); + } + return 400; + } + + @Override protected double computeMinHeight(double width) { + String text = getText(); + Node graphic = getGraphic(); + if ((text == null || text.isEmpty()) && (graphic != null)) { + return graphic.minHeight(width); + } + return 100; + } + + @Override public boolean isShowFromTop() { + return NotificationPopupHandler.this.isShowFromTop(notification.position); + } + + @Override public void hide() { + isShowing = false; + + // this would slide the notification bar out of view, + // but I prefer the fade out below + // doHide(); + + // animate out the popup by fading it + createHideTimeline(popup, this, p, Duration.ZERO).play(); + } + + @Override public boolean isCloseButtonVisible() { + return !notification.hideCloseButton; + } + + @Override public double getContainerHeight() { + return startY + screenHeight; + } + + @Override public void relocateInParent(double x, double y) { + // this allows for us to slide the notification upwards + switch (p) { + case BOTTOM_LEFT: + case BOTTOM_CENTER: + case BOTTOM_RIGHT: + popup.setAnchorY(y - padding); + break; + default: + // no-op + break; + } + } + }; + + notificationBar.getStyleClass().addAll(notification.styleClass); + + notificationBar.setOnMouseClicked(e -> { + if (notification.onAction != null) { + ActionEvent actionEvent = new ActionEvent(notificationBar, notificationBar); + notification.onAction.handle(actionEvent); + + // animate out the popup + createHideTimeline(popup, notificationBar, p, Duration.ZERO).play(); + } + }); + + popup.getContent().add(notificationBar); + popup.show(owner, 0, 0); + + // determine location for the popup + double anchorX = 0, anchorY = 0; + final double barWidth = notificationBar.getWidth(); + final double barHeight = notificationBar.getHeight(); + + // get anchorX + switch (p) { + case TOP_LEFT: + case CENTER_LEFT: + case BOTTOM_LEFT: + anchorX = padding + startX; + break; + + case TOP_CENTER: + case CENTER: + case BOTTOM_CENTER: + anchorX = startX + (screenWidth / 2.0) - barWidth / 2.0 - padding / 2.0; + break; + + default: + case TOP_RIGHT: + case CENTER_RIGHT: + case BOTTOM_RIGHT: + anchorX = startX + screenWidth - barWidth - padding; + break; + } + + // get anchorY + switch (p) { + case TOP_LEFT: + case TOP_CENTER: + case TOP_RIGHT: + anchorY = padding + startY; + break; + + case CENTER_LEFT: + case CENTER: + case CENTER_RIGHT: + anchorY = startY + (screenHeight / 2.0) - barHeight / 2.0 - padding / 2.0; + break; + + default: + case BOTTOM_LEFT: + case BOTTOM_CENTER: + case BOTTOM_RIGHT: + anchorY = startY + screenHeight - barHeight - padding; + break; + } + + popup.setAnchorX(anchorX); + popup.setAnchorY(anchorY); + + isShowing = true; + notificationBar.doShow(); + + addPopupToMap(p, popup); + + // begin a timeline to get rid of the popup + Timeline timeline = createHideTimeline(popup, notificationBar, p, notification.hideAfterDuration); + timeline.play(); + } + + private void hide(Popup popup, Pos p) { + popup.hide(); + removePopupFromMap(p, popup); + } + + private Timeline createHideTimeline(final Popup popup, NotificationBar bar, final Pos p, Duration startDelay) { + KeyValue fadeOutBegin = new KeyValue(bar.opacityProperty(), 1.0); + KeyValue fadeOutEnd = new KeyValue(bar.opacityProperty(), 0.0); + + KeyFrame kfBegin = new KeyFrame(Duration.ZERO, fadeOutBegin); + KeyFrame kfEnd = new KeyFrame(Duration.millis(500), fadeOutEnd); + + Timeline timeline = new Timeline(kfBegin, kfEnd); + timeline.setDelay(startDelay); + timeline.setOnFinished(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + hide(popup, p); + } + }); + + return timeline; + } + + private void addPopupToMap(Pos p, Popup popup) { + List<Popup> popups; + if (!popupsMap.containsKey(p)) { + popups = new LinkedList<>(); + popupsMap.put(p, popups); + } else { + popups = popupsMap.get(p); + } + + doAnimation(p, popup); + + // add the popup to the list so it is kept in memory and can be + // accessed later on + popups.add(popup); + } + + private void removePopupFromMap(Pos p, Popup popup) { + if (popupsMap.containsKey(p)) { + List<Popup> popups = popupsMap.get(p); + popups.remove(popup); + } + } + + private void doAnimation(Pos p, Popup changedPopup) { + List<Popup> popups = popupsMap.get(p); + if (popups == null) { + return; + } + + final double newPopupHeight = changedPopup.getContent().get(0).getBoundsInParent().getHeight(); + + parallelTransition.stop(); + parallelTransition.getChildren().clear(); + + final boolean isShowFromTop = isShowFromTop(p); + + // animate all other popups in the list upwards so that the new one + // is in the 'new' area. + // firstly, we need to determine the target positions for all popups + double sum = 0; + double targetAnchors[] = new double[popups.size()]; + for (int i = popups.size() - 1; i >= 0; i--) { + Popup _popup = popups.get(i); + + final double popupHeight = _popup.getContent().get(0).getBoundsInParent().getHeight(); + + if (isShowFromTop) { + if (i == popups.size() - 1) { + sum = startY + newPopupHeight + padding; + } else { + sum += popupHeight; + } + targetAnchors[i] = sum; + } else { + if (i == popups.size() - 1) { + sum = changedPopup.getAnchorY() - popupHeight; + } else { + sum -= popupHeight; + } + + targetAnchors[i] = sum; + } + } + + // then we set up animations for each popup to animate towards the + // target + for (int i = popups.size() - 1; i >= 0; i--) { + final Popup _popup = popups.get(i); + final double anchorYTarget = targetAnchors[i]; + if(anchorYTarget < 0){ + _popup.hide(); + } + final double oldAnchorY = _popup.getAnchorY(); + final double distance = anchorYTarget - oldAnchorY; + + Transition t = new CustomTransition(_popup, oldAnchorY, distance); + t.setCycleCount(1); + parallelTransition.getChildren().add(t); + } + parallelTransition.play(); + } + + private boolean isShowFromTop(Pos p) { + switch (p) { + case TOP_LEFT: + case TOP_CENTER: + case TOP_RIGHT: + return true; + default: + return false; + } + } + + class CustomTransition extends Transition { + + private WeakReference<Popup> popupWeakReference; + private double oldAnchorY; + private double distance; + + CustomTransition(Popup popup, double oldAnchorY, double distance) { + popupWeakReference = new WeakReference<>(popup); + this.oldAnchorY = oldAnchorY; + this.distance = distance; + setCycleDuration(Duration.millis(350.0)); + } + + @Override + protected void interpolate(double frac) { + Popup popup = popupWeakReference.get(); + if (popup != null) { + double newAnchorY = oldAnchorY + distance * frac; + popup.setAnchorY(newAnchorY); + } + } + + } + } +} + diff --git a/src/org/controlsfx/control/PlusMinusSlider.java b/src/org/controlsfx/control/PlusMinusSlider.java new file mode 100644 index 0000000000000000000000000000000000000000..8749ffd02aa607b84178007b08ebff41b2d5a53d --- /dev/null +++ b/src/org/controlsfx/control/PlusMinusSlider.java @@ -0,0 +1,345 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.PlusMinusSliderSkin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.property.ReadOnlyDoubleWrapper; +import javafx.collections.MapChangeListener; +import javafx.css.CssMetaData; +import javafx.css.PseudoClass; +import javafx.css.Styleable; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.event.EventType; +import javafx.geometry.Orientation; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.input.InputEvent; + +import com.sun.javafx.css.converters.EnumConverter; + +/** + * A plus minus slider allows the user to continously fire an event carrying a + * value between -1 and +1 by moving a thumb from its center position to the + * left or right (or top and bottom) edge of the control. The thumb will + * automatically center itself again on the zero position when the user lets go + * of the mouse button. Scrolling through a large list of items at different + * speeds is one possible use case for a control like this. + * + * <center> <img src="plus-minus-slider.png" alt="Screenshot of PlusMinusSlider"> </center> + */ +public class PlusMinusSlider extends ControlsFXControl { + + private static final String DEFAULT_STYLE_CLASS = "plus-minus-slider"; //$NON-NLS-1$ + + private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = PseudoClass + .getPseudoClass("vertical"); //$NON-NLS-1$ + + private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = PseudoClass + .getPseudoClass("horizontal"); //$NON-NLS-1$ + + /** + * Constructs a new adjuster control with a default horizontal orientation. + */ + public PlusMinusSlider() { + getStyleClass().add(DEFAULT_STYLE_CLASS); + + setOrientation(Orientation.HORIZONTAL); + + /* + * We are "abusing" the properties map to pass the new value of the + * slider from the skin to the control. It has to be done this way + * because the value property of this control is "read-only". + */ + getProperties().addListener(new MapChangeListener<Object, Object>() { + @Override + public void onChanged(MapChangeListener.Change<? extends Object, ? extends Object> change) { + if (change.getKey().equals("plusminusslidervalue")) { //$NON-NLS-1$ + if (change.getValueAdded() != null) { + Double valueAdded = (Double) change.getValueAdded(); + value.set(valueAdded); + change.getMap().remove("plusminusslidervalue"); //$NON-NLS-1$ + } + } + }; + }); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(PlusMinusSlider.class, "plusminusslider.css"); + } + + @Override + protected Skin<?> createDefaultSkin() { + return new PlusMinusSliderSkin(this); + } + + private ReadOnlyDoubleWrapper value = new ReadOnlyDoubleWrapper(this, + "value", 0); //$NON-NLS-1$ + + /** + * Returns the value property of the adjuster. The value is always between + * -1 and +1. + * + * @return the value of the adjuster + */ + public final ReadOnlyDoubleProperty valueProperty() { + return value.getReadOnlyProperty(); + } + + /** + * Returns the value of the value property. + * + * @return the value of the adjuster [-1, +1] + * + * @see #valueProperty() + */ + public final double getValue() { + return valueProperty().get(); + } + + // orientation + + private ObjectProperty<Orientation> orientation; + + /** + * Sets the value of the orientation property. + * + * @param value + * the new orientation (horizontal, vertical). + * @see #orientationProperty() + */ + public final void setOrientation(Orientation value) { + orientationProperty().set(value); + } + + /** + * Returns the value of the orientation property. + * + * @return the current orientation of the control + * @see #orientationProperty() + */ + public final Orientation getOrientation() { + return orientation == null ? Orientation.HORIZONTAL : orientation.get(); + } + + /** + * Returns the stylable object property used for storing the orientation of + * the adjuster control. The CSS property "-fx-orientation" can be used to + * initialize this value. + * + * @return the orientation property + */ + public final ObjectProperty<Orientation> orientationProperty() { + if (orientation == null) { + orientation = new StyleableObjectProperty<Orientation>(null) { + @Override + protected void invalidated() { + final boolean vertical = (get() == Orientation.VERTICAL); + pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, + vertical); + pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, + !vertical); + } + + @Override + public CssMetaData<PlusMinusSlider, Orientation> getCssMetaData() { + return StyleableProperties.ORIENTATION; + } + + @Override + public Object getBean() { + return PlusMinusSlider.this; + } + + @Override + public String getName() { + return "orientation"; //$NON-NLS-1$ + } + }; + } + return orientation; + } + + // event support + + /** + * Stores the event handler that will be informed when the adjuster's value + * changes. + * + * @return the value change event handler property + */ + public final ObjectProperty<EventHandler<PlusMinusEvent>> onValueChangedProperty() { + return onValueChanged; + } + + /** + * Sets an event handler that will receive plus minus events when the user + * moves the adjuster's thumb. + * + * @param value + * the event handler + * + * @see #onValueChangedProperty() + */ + public final void setOnValueChanged(EventHandler<PlusMinusEvent> value) { + onValueChangedProperty().set(value); + } + + /** + * Returns the event handler that will be notified when the adjuster's value + * changes. + * + * @return An EventHandler. + */ + public final EventHandler<PlusMinusEvent> getOnValueChanged() { + return onValueChangedProperty().get(); + } + + private ObjectProperty<EventHandler<PlusMinusEvent>> onValueChanged = new ObjectPropertyBase<EventHandler<PlusMinusEvent>>() { + + @Override + protected void invalidated() { + setEventHandler(PlusMinusEvent.VALUE_CHANGED, get()); + } + + @Override + public Object getBean() { + return PlusMinusSlider.this; + } + + @Override + public String getName() { + return "onValueChanged"; //$NON-NLS-1$ + } + }; + + private static class StyleableProperties { + + private static final CssMetaData<PlusMinusSlider, Orientation> ORIENTATION = new CssMetaData<PlusMinusSlider, Orientation>( + "-fx-orientation", new EnumConverter<>( //$NON-NLS-1$ + Orientation.class), Orientation.VERTICAL) { + + @Override + public Orientation getInitialValue(PlusMinusSlider node) { + // A vertical Slider should remain vertical + return node.getOrientation(); + } + + @Override + public boolean isSettable(PlusMinusSlider n) { + return n.orientation == null || !n.orientation.isBound(); + } + + @SuppressWarnings("unchecked") + @Override + public StyleableProperty<Orientation> getStyleableProperty( + PlusMinusSlider n) { + return (StyleableProperty<Orientation>) n.orientationProperty(); + } + }; + + private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; + static { + final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData()); + styleables.add(ORIENTATION); + + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + /** + * @return The CssMetaData associated with this class, which may include the + * CssMetaData of its super classes. + * @since JavaFX 8.0 + */ + public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + /** + * An event class used by the {@link PlusMinusSlider} to inform event + * handlers about changes. + */ + public static class PlusMinusEvent extends InputEvent { + + private static final long serialVersionUID = 2881004583512990781L; + + public static final EventType<PlusMinusEvent> ANY = new EventType<>( + InputEvent.ANY, "ANY"); //$NON-NLS-1$ + + /** + * An event type used when the value property ( + * {@link PlusMinusSlider#valueProperty()}) changes. + */ + public static final EventType<PlusMinusEvent> VALUE_CHANGED = new EventType<>( + PlusMinusEvent.ANY, "VALUE_CHANGED"); //$NON-NLS-1$ + + private double value; + + /** + * Constructs a new event object. + * + * @param source + * the source of the event (always the + * {@link PlusMinusSlider}) + * @param target + * the target of the event (always the + * {@link PlusMinusSlider}) + * @param eventType + * the type of the event (e.g. {@link #VALUE_CHANGED}) + * @param value + * the actual current value of the adjuster + */ + public PlusMinusEvent(Object source, EventTarget target, + EventType<? extends InputEvent> eventType, double value) { + super(source, target, eventType); + + this.value = value; + } + + /** + * The value of the {@link PlusMinusSlider}. + * + * @return the value + */ + public double getValue() { + return value; + } + } +} diff --git a/src/org/controlsfx/control/PopOver.java b/src/org/controlsfx/control/PopOver.java new file mode 100644 index 0000000000000000000000000000000000000000..b98b4e54c6a423197310c0a81dca9cb2b263386c --- /dev/null +++ b/src/org/controlsfx/control/PopOver.java @@ -0,0 +1,1011 @@ +/** + * Copyright (c) 2013, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.PopOverSkin; +import javafx.animation.FadeTransition; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.WeakInvalidationListener; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WeakChangeListener; +import javafx.event.EventHandler; +import javafx.event.WeakEventHandler; +import javafx.geometry.Bounds; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.PopupControl; +import javafx.scene.control.Skin; +import javafx.scene.layout.StackPane; +import javafx.stage.Window; +import javafx.stage.WindowEvent; +import javafx.util.Duration; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; +import static java.util.Objects.requireNonNull; +import static javafx.scene.input.MouseEvent.MOUSE_CLICKED; + +/** + * The PopOver control provides detailed information about an owning node in a + * popup window. The popup window has a very lightweight appearance (no default + * window decorations) and an arrow pointing at the owner. Due to the nature of + * popup windows the PopOver will move around with the parent window when the + * user drags it. <br> + * <center> <img src="popover.png" alt="Screenshot of PopOver"> </center> <br> + * The PopOver can be detached from the owning node by dragging it away from the + * owner. It stops displaying an arrow and starts displaying a title and a close + * icon. <br> + * <br> + * <center> <img src="popover-detached.png" + * alt="Screenshot of a detached PopOver"> </center> <br> + * The following image shows a popover with an accordion content node. PopOver + * controls are automatically resizing themselves when the content node changes + * its size.<br> + * <br> + * <center> <img src="popover-accordion.png" + * alt="Screenshot of PopOver containing an Accordion"> </center> <br> + * For styling apply stylesheets to the root pane of the PopOver. + * + * <h3>Example:</h3> + * + * <pre> + * PopOver popOver = new PopOver(); + * popOver.getRoot().getStylesheets().add(...); + * </pre> + * + */ +public class PopOver extends PopupControl { + + private static final String DEFAULT_STYLE_CLASS = "popover"; //$NON-NLS-1$ + + private static final Duration DEFAULT_FADE_DURATION = Duration.seconds(.2); + + private double targetX; + + private double targetY; + + private final SimpleBooleanProperty animated = new SimpleBooleanProperty(true); + private final ObjectProperty<Duration> fadeInDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION); + private final ObjectProperty<Duration> fadeOutDuration = new SimpleObjectProperty<>(DEFAULT_FADE_DURATION); + + /** + * Creates a pop over with a label as the content node. + */ + public PopOver() { + super(); + + getStyleClass().add(DEFAULT_STYLE_CLASS); + + getRoot().getStylesheets().add( + PopOver.class.getResource("popover.css").toExternalForm()); //$NON-NLS-1$ + + setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT); + setOnHiding(new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent evt) { + setDetached(false); + } + }); + + /* + * Create some initial content. + */ + Label label = new Label(localize(asKey("popOver.default.content"))); //$NON-NLS-1$ + label.setPrefSize(200, 200); + label.setPadding(new Insets(4)); + setContentNode(label); + + InvalidationListener repositionListener = observable -> { + if (isShowing() && !isDetached()) { + show(getOwnerNode(), targetX, targetY); + adjustWindowLocation(); + } + }; + + arrowSize.addListener(repositionListener); + cornerRadius.addListener(repositionListener); + arrowLocation.addListener(repositionListener); + arrowIndent.addListener(repositionListener); + headerAlwaysVisible.addListener(repositionListener); + + /* + * A detached popover should of course not automatically hide itself. + */ + detached.addListener(it -> { + if (isDetached()) { + setAutoHide(false); + } else { + setAutoHide(true); + } + }); + + setAutoHide(true); + } + + /** + * Creates a pop over with the given node as the content node. + * + * @param content + * The content shown by the pop over + */ + public PopOver(Node content) { + this(); + + setContentNode(content); + } + + @Override + protected Skin<?> createDefaultSkin() { + return new PopOverSkin(this); + } + + private final StackPane root = new StackPane(); + + /** + * The root pane stores the content node of the popover. It is accessible + * via this method in order to support proper styling. + * + * <h3>Example:</h3> + * + * <pre> + * PopOver popOver = new PopOver(); + * popOver.getRoot().getStylesheets().add(...); + * </pre> + * + * @return the root pane + */ + public final StackPane getRoot() { + return root; + } + + // Content support. + + private final ObjectProperty<Node> contentNode = new SimpleObjectProperty<Node>( + this, "contentNode") { //$NON-NLS-1$ + @Override + public void setValue(Node node) { + if (node == null) { + throw new IllegalArgumentException( + "content node can not be null"); //$NON-NLS-1$ + } + }; + }; + + /** + * Returns the content shown by the pop over. + * + * @return the content node property + */ + public final ObjectProperty<Node> contentNodeProperty() { + return contentNode; + } + + /** + * Returns the value of the content property + * + * @return the content node + * + * @see #contentNodeProperty() + */ + public final Node getContentNode() { + return contentNodeProperty().get(); + } + + /** + * Sets the value of the content property. + * + * @param content + * the new content node value + * + * @see #contentNodeProperty() + */ + public final void setContentNode(Node content) { + contentNodeProperty().set(content); + } + + private InvalidationListener hideListener = new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + if (!isDetached()) { + hide(Duration.ZERO); + } + } + }; + + private WeakInvalidationListener weakHideListener = new WeakInvalidationListener( + hideListener); + + private ChangeListener<Number> xListener = new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> value, + Number oldX, Number newX) { + if (!isDetached()) { + setAnchorX(getAnchorX() + (newX.doubleValue() - oldX.doubleValue())); + } + } + }; + + private WeakChangeListener<Number> weakXListener = new WeakChangeListener<>( + xListener); + + private ChangeListener<Number> yListener = new ChangeListener<Number>() { + @Override + public void changed(ObservableValue<? extends Number> value, + Number oldY, Number newY) { + if (!isDetached()) { + setAnchorY(getAnchorY() + (newY.doubleValue() - oldY.doubleValue())); + } + } + }; + + private WeakChangeListener<Number> weakYListener = new WeakChangeListener<>( + yListener); + + private Window ownerWindow; + private final EventHandler<WindowEvent> closePopOverOnOwnerWindowCloseLambda = event -> ownerWindowClosing(); + private final WeakEventHandler<WindowEvent> closePopOverOnOwnerWindowClose = new WeakEventHandler<>(closePopOverOnOwnerWindowCloseLambda); + + /** + * Shows the pop over in a position relative to the edges of the given owner + * node. The position is dependent on the arrow location. If the arrow is + * pointing to the right then the pop over will be placed to the left of the + * given owner. If the arrow points up then the pop over will be placed + * below the given owner node. The arrow will slightly overlap with the + * owner node. + * + * @param owner + * the owner of the pop over + */ + public final void show(Node owner) { + show(owner, 4); + } + + /** + * Shows the pop over in a position relative to the edges of the given owner + * node. The position is dependent on the arrow location. If the arrow is + * pointing to the right then the pop over will be placed to the left of the + * given owner. If the arrow points up then the pop over will be placed + * below the given owner node. + * + * @param owner + * the owner of the pop over + * @param offset + * if negative specifies the distance to the owner node or when + * positive specifies the number of pixels that the arrow will + * overlap with the owner node (positive values are recommended) + */ + public final void show(Node owner, double offset) { + requireNonNull(owner); + + Bounds bounds = owner.localToScreen(owner.getBoundsInLocal()); + + switch (getArrowLocation()) { + case BOTTOM_CENTER: + case BOTTOM_LEFT: + case BOTTOM_RIGHT: + show(owner, bounds.getMinX() + bounds.getWidth() / 2, + bounds.getMinY() + offset); + break; + case LEFT_BOTTOM: + case LEFT_CENTER: + case LEFT_TOP: + show(owner, bounds.getMaxX() - offset, + bounds.getMinY() + bounds.getHeight() / 2); + break; + case RIGHT_BOTTOM: + case RIGHT_CENTER: + case RIGHT_TOP: + show(owner, bounds.getMinX() + offset, + bounds.getMinY() + bounds.getHeight() / 2); + break; + case TOP_CENTER: + case TOP_LEFT: + case TOP_RIGHT: + show(owner, bounds.getMinX() + bounds.getWidth() / 2, + bounds.getMinY() + bounds.getHeight() - offset); + break; + default: + break; + } + } + + /** {@inheritDoc} */ + @Override + public final void show(Window owner) { + super.show(owner); + ownerWindow = owner; + + if (isAnimated()) { + showFadeInAnimation(getFadeInDuration()); + } + + ownerWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, + closePopOverOnOwnerWindowClose); + ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING, + closePopOverOnOwnerWindowClose); + } + + /** {@inheritDoc} */ + @Override + public final void show(Window ownerWindow, double anchorX, double anchorY) { + super.show(ownerWindow, anchorX, anchorY); + this.ownerWindow = ownerWindow; + + if (isAnimated()) { + showFadeInAnimation(getFadeInDuration()); + } + + ownerWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, + closePopOverOnOwnerWindowClose); + ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING, + closePopOverOnOwnerWindowClose); + } + + /** + * Makes the pop over visible at the give location and associates it with + * the given owner node. The x and y coordinate will be the target location + * of the arrow of the pop over and not the location of the window. + * + * @param owner + * the owning node + * @param x + * the x coordinate for the pop over arrow tip + * @param y + * the y coordinate for the pop over arrow tip + */ + @Override + public final void show(Node owner, double x, double y) { + show(owner, x, y, getFadeInDuration()); + } + + /** + * Makes the pop over visible at the give location and associates it with + * the given owner node. The x and y coordinate will be the target location + * of the arrow of the pop over and not the location of the window. + * + * @param owner + * the owning node + * @param x + * the x coordinate for the pop over arrow tip + * @param y + * the y coordinate for the pop over arrow tip + * @param fadeInDuration + * the time it takes for the pop over to be fully visible. This duration takes precedence over the fade-in property without setting. + */ + public final void show(Node owner, double x, double y, + Duration fadeInDuration) { + + /* + * Calling show() a second time without first closing the pop over + * causes it to be placed at the wrong location. + */ + if (ownerWindow != null && isShowing()) { + super.hide(); + } + + targetX = x; + targetY = y; + + if (owner == null) { + throw new IllegalArgumentException("owner can not be null"); //$NON-NLS-1$ + } + + if (fadeInDuration == null) { + fadeInDuration = DEFAULT_FADE_DURATION; + } + + /* + * This is all needed because children windows do not get their x and y + * coordinate updated when the owning window gets moved by the user. + */ + if (ownerWindow != null) { + ownerWindow.xProperty().removeListener(weakXListener); + ownerWindow.yProperty().removeListener(weakYListener); + ownerWindow.widthProperty().removeListener(weakHideListener); + ownerWindow.heightProperty().removeListener(weakHideListener); + } + + ownerWindow = owner.getScene().getWindow(); + ownerWindow.xProperty().addListener(weakXListener); + ownerWindow.yProperty().addListener(weakYListener); + ownerWindow.widthProperty().addListener(weakHideListener); + ownerWindow.heightProperty().addListener(weakHideListener); + + setOnShown(evt -> { + + /* + * The user clicked somewhere into the transparent background. If + * this is the case then hide the window (when attached). + */ + getScene().addEventHandler(MOUSE_CLICKED, mouseEvent -> { + if (mouseEvent.getTarget().equals(getScene().getRoot())) { + if (!isDetached()) { + hide(); + } + } + }); + + /* + * Move the window so that the arrow will end up pointing at the + * target coordinates. + */ + adjustWindowLocation(); + }); + + super.show(owner, x, y); + + if (isAnimated()) { + showFadeInAnimation(fadeInDuration); + } + + // Bug fix - close popup when owner window is closing + ownerWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, + closePopOverOnOwnerWindowClose); + ownerWindow.addEventFilter(WindowEvent.WINDOW_HIDING, + closePopOverOnOwnerWindowClose); + } + + private void showFadeInAnimation(Duration fadeInDuration) { + // Fade In + Node skinNode = getSkin().getNode(); + skinNode.setOpacity(0); + + FadeTransition fadeIn = new FadeTransition(fadeInDuration, skinNode); + fadeIn.setFromValue(0); + fadeIn.setToValue(1); + fadeIn.play(); + } + + private void ownerWindowClosing() { + hide(Duration.ZERO); + } + + /** + * Hides the pop over by quickly changing its opacity to 0. + * + * @see #hide(Duration) + */ + @Override + public final void hide() { + hide(getFadeOutDuration()); + } + + /** + * Hides the pop over by quickly changing its opacity to 0. + * + * @param fadeOutDuration + * the duration of the fade transition that is being used to + * change the opacity of the pop over + * @since 1.0 + */ + public final void hide(Duration fadeOutDuration) { + //We must remove EventFilter in order to prevent memory leak. + if (ownerWindow != null){ + ownerWindow.removeEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, + closePopOverOnOwnerWindowClose); + ownerWindow.removeEventFilter(WindowEvent.WINDOW_HIDING, + closePopOverOnOwnerWindowClose); + } + if (fadeOutDuration == null) { + fadeOutDuration = DEFAULT_FADE_DURATION; + } + + if (isShowing()) { + if (isAnimated()) { + // Fade Out + Node skinNode = getSkin().getNode(); + + FadeTransition fadeOut = new FadeTransition(fadeOutDuration, + skinNode); + fadeOut.setFromValue(skinNode.getOpacity()); + fadeOut.setToValue(0); + fadeOut.setOnFinished(evt -> super.hide()); + fadeOut.play(); + } else { + super.hide(); + } + } + } + + private void adjustWindowLocation() { + Bounds bounds = PopOver.this.getSkin().getNode().getBoundsInParent(); + + switch (getArrowLocation()) { + case TOP_CENTER: + case TOP_LEFT: + case TOP_RIGHT: + setAnchorX(getAnchorX() + bounds.getMinX() - computeXOffset()); + setAnchorY(getAnchorY() + bounds.getMinY() + getArrowSize()); + break; + case LEFT_TOP: + case LEFT_CENTER: + case LEFT_BOTTOM: + setAnchorX(getAnchorX() + bounds.getMinX() + getArrowSize()); + setAnchorY(getAnchorY() + bounds.getMinY() - computeYOffset()); + break; + case BOTTOM_CENTER: + case BOTTOM_LEFT: + case BOTTOM_RIGHT: + setAnchorX(getAnchorX() + bounds.getMinX() - computeXOffset()); + setAnchorY(getAnchorY() - bounds.getMinY() - bounds.getMaxY() - 1); + break; + case RIGHT_TOP: + case RIGHT_BOTTOM: + case RIGHT_CENTER: + setAnchorX(getAnchorX() - bounds.getMinX() - bounds.getMaxX() - 1); + setAnchorY(getAnchorY() + bounds.getMinY() - computeYOffset()); + break; + } + } + + private double computeXOffset() { + switch (getArrowLocation()) { + case TOP_LEFT: + case BOTTOM_LEFT: + return getCornerRadius() + getArrowIndent() + getArrowSize(); + case TOP_CENTER: + case BOTTOM_CENTER: + return getContentNode().prefWidth(-1) / 2; + case TOP_RIGHT: + case BOTTOM_RIGHT: + return getContentNode().prefWidth(-1) - getArrowIndent() + - getCornerRadius() - getArrowSize(); + default: + return 0; + } + } + + private double computeYOffset() { + double prefContentHeight = getContentNode().prefHeight(-1); + + switch (getArrowLocation()) { + case LEFT_TOP: + case RIGHT_TOP: + return getCornerRadius() + getArrowIndent() + getArrowSize(); + case LEFT_CENTER: + case RIGHT_CENTER: + return Math.max(prefContentHeight, 2 * (getCornerRadius() + + getArrowIndent() + getArrowSize())) / 2; + case LEFT_BOTTOM: + case RIGHT_BOTTOM: + return Math.max(prefContentHeight - getCornerRadius() + - getArrowIndent() - getArrowSize(), getCornerRadius() + + getArrowIndent() + getArrowSize()); + default: + return 0; + } + } + + /** + * Detaches the pop over from the owning node. The pop over will no longer + * display an arrow pointing at the owner node. + */ + public final void detach() { + if (isDetachable()) { + setDetached(true); + } + } + + // always show header + + private final BooleanProperty headerAlwaysVisible = new SimpleBooleanProperty(this, "headerAlwaysVisible"); //$NON-NLS-1$ + + /** + * Determines whether or not the {@link PopOver} header should remain visible, even while attached. + */ + public final BooleanProperty headerAlwaysVisibleProperty() { + return headerAlwaysVisible; + } + + /** + * Sets the value of the headerAlwaysVisible property. + * + * @param visible + * if true, then the header is visible even while attached + * + * @see #headerAlwaysVisibleProperty() + */ + public final void setHeaderAlwaysVisible(boolean visible) { + headerAlwaysVisible.setValue(visible); + } + + /** + * Returns the value of the detachable property. + * + * @return true if the header is visible even while attached + * + * @see #headerAlwaysVisibleProperty() + */ + public final boolean isHeaderAlwaysVisible() { + return headerAlwaysVisible.getValue(); + } + + + // detach support + + private final BooleanProperty detachable = new SimpleBooleanProperty(this, + "detachable", true); //$NON-NLS-1$ + + /** + * Determines if the pop over is detachable at all. + */ + public final BooleanProperty detachableProperty() { + return detachable; + } + + /** + * Sets the value of the detachable property. + * + * @param detachable + * if true then the user can detach / tear off the pop over + * + * @see #detachableProperty() + */ + public final void setDetachable(boolean detachable) { + detachableProperty().set(detachable); + } + + /** + * Returns the value of the detachable property. + * + * @return true if the user is allowed to detach / tear off the pop over + * + * @see #detachableProperty() + */ + public final boolean isDetachable() { + return detachableProperty().get(); + } + + private final BooleanProperty detached = new SimpleBooleanProperty(this, + "detached", false); //$NON-NLS-1$ + + /** + * Determines whether the pop over is detached from the owning node or not. + * A detached pop over no longer shows an arrow pointing at the owner and + * features its own title bar. + * + * @return the detached property + */ + public final BooleanProperty detachedProperty() { + return detached; + } + + /** + * Sets the value of the detached property. + * + * @param detached + * if true the pop over will change its apperance to "detached" + * mode + * + * @see #detachedProperty() + */ + public final void setDetached(boolean detached) { + detachedProperty().set(detached); + } + + /** + * Returns the value of the detached property. + * + * @return true if the pop over is currently detached. + * + * @see #detachedProperty() + */ + public final boolean isDetached() { + return detachedProperty().get(); + } + + // arrow size support + + // TODO: make styleable + + private final DoubleProperty arrowSize = new SimpleDoubleProperty(this, + "arrowSize", 12); //$NON-NLS-1$ + + /** + * Controls the size of the arrow. Default value is 12. + * + * @return the arrow size property + */ + public final DoubleProperty arrowSizeProperty() { + return arrowSize; + } + + /** + * Returns the value of the arrow size property. + * + * @return the arrow size property value + * + * @see #arrowSizeProperty() + */ + public final double getArrowSize() { + return arrowSizeProperty().get(); + } + + /** + * Sets the value of the arrow size property. + * + * @param size + * the new value of the arrow size property + * + * @see #arrowSizeProperty() + */ + public final void setArrowSize(double size) { + arrowSizeProperty().set(size); + } + + // arrow indent support + + // TODO: make styleable + + private final DoubleProperty arrowIndent = new SimpleDoubleProperty(this, + "arrowIndent", 12); //$NON-NLS-1$ + + /** + * Controls the distance between the arrow and the corners of the pop over. + * The default value is 12. + * + * @return the arrow indent property + */ + public final DoubleProperty arrowIndentProperty() { + return arrowIndent; + } + + /** + * Returns the value of the arrow indent property. + * + * @return the arrow indent value + * + * @see #arrowIndentProperty() + */ + public final double getArrowIndent() { + return arrowIndentProperty().get(); + } + + /** + * Sets the value of the arrow indent property. + * + * @param size + * the arrow indent value + * + * @see #arrowIndentProperty() + */ + public final void setArrowIndent(double size) { + arrowIndentProperty().set(size); + } + + // radius support + + // TODO: make styleable + + private final DoubleProperty cornerRadius = new SimpleDoubleProperty(this, + "cornerRadius", 6); //$NON-NLS-1$ + + /** + * Returns the corner radius property for the pop over. + * + * @return the corner radius property (default is 6) + */ + public final DoubleProperty cornerRadiusProperty() { + return cornerRadius; + } + + /** + * Returns the value of the corner radius property. + * + * @return the corner radius + * + * @see #cornerRadiusProperty() + */ + public final double getCornerRadius() { + return cornerRadiusProperty().get(); + } + + /** + * Sets the value of the corner radius property. + * + * @param radius + * the corner radius + * + * @see #cornerRadiusProperty() + */ + public final void setCornerRadius(double radius) { + cornerRadiusProperty().set(radius); + } + + // Detached stage title + + private final StringProperty title = new SimpleStringProperty(this, "title", localize(asKey("popOver.default.title"))); //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Stores the title to display in the PopOver's header. + * + * @return the title property + */ + public final StringProperty titleProperty() { + return title; + } + + /** + * Returns the value of the title property. + * + * @return the detached title + * @see #titleProperty() + */ + public final String getTitle() { + return titleProperty().get(); + } + + /** + * Sets the value of the title property. + * + * @param title the title to use when detached + * @see #titleProperty() + */ + public final void setTitle(String title) { + if (title == null) { + throw new IllegalArgumentException("title can not be null"); //$NON-NLS-1$ + } + + titleProperty().set(title); + } + + private final ObjectProperty<ArrowLocation> arrowLocation = new SimpleObjectProperty<>( + this, "arrowLocation", ArrowLocation.LEFT_TOP); //$NON-NLS-1$ + + /** + * Stores the preferred arrow location. This might not be the actual + * location of the arrow if auto fix is enabled. + * + * @see #setAutoFix(boolean) + * + * @return the arrow location property + */ + public final ObjectProperty<ArrowLocation> arrowLocationProperty() { + return arrowLocation; + } + + /** + * Sets the value of the arrow location property. + * + * @see #arrowLocationProperty() + * + * @param location + * the requested location + */ + public final void setArrowLocation(ArrowLocation location) { + arrowLocationProperty().set(location); + } + + /** + * Returns the value of the arrow location property. + * + * @see #arrowLocationProperty() + * + * @return the preferred arrow location + */ + public final ArrowLocation getArrowLocation() { + return arrowLocationProperty().get(); + } + + /** + * All possible arrow locations. + */ + public enum ArrowLocation { + LEFT_TOP, LEFT_CENTER, LEFT_BOTTOM, RIGHT_TOP, RIGHT_CENTER, RIGHT_BOTTOM, TOP_LEFT, TOP_CENTER, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT; + } + + /** + * Stores the fade-in duration. This should be set before calling PopOver.show(..). + * + * @return the fade-in duration property + */ + public final ObjectProperty<Duration> fadeInDurationProperty() { + return fadeInDuration; + } + + /** + * Stores the fade-out duration. + * + * @return the fade-out duration property + */ + public final ObjectProperty<Duration> fadeOutDurationProperty() { + return fadeOutDuration; + } + + /** + * Returns the value of the fade-in duration property. + * + * @return the fade-in duration + * @see #fadeInDurationProperty() + */ + public final Duration getFadeInDuration() { + return fadeInDurationProperty().get(); + } + + /** + * Sets the value of the fade-in duration property. This should be set before calling PopOver.show(..). + * + * @param duration the requested fade-in duration + * @see #fadeInDurationProperty() + */ + public final void setFadeInDuration(Duration duration) { + fadeInDurationProperty().setValue(duration); + } + + /** + * Returns the value of the fade-out duration property. + * + * @return the fade-out duration + * @see #fadeOutDurationProperty() + */ + public final Duration getFadeOutDuration() { + return fadeOutDurationProperty().get(); + } + + /** + * Sets the value of the fade-out duration property. + * + * @param duration the requested fade-out duration + * @see #fadeOutDurationProperty() + */ + public final void setFadeOutDuration(Duration duration) { + fadeOutDurationProperty().setValue(duration); + } + + /** + * Stores the "animated" flag. If true then the PopOver will be shown / hidden with a short fade in / out animation. + * + * @return the "animated" property + */ + public final BooleanProperty animatedProperty() { + return animated; + } + + /** + * Returns the value of the "animated" property. + * + * @return true if the PopOver will be shown and hidden with a short fade animation + * @see #animatedProperty() + */ + public final boolean isAnimated() { + return animatedProperty().get(); + } + + /** + * Sets the value of the "animated" property. + * + * @param animated if true the PopOver will be shown and hidden with a short fade animation + * @see #animatedProperty() + */ + public final void setAnimated(boolean animated) { + animatedProperty().set(animated); + } +} diff --git a/src/org/controlsfx/control/PrefixSelectionChoiceBox.java b/src/org/controlsfx/control/PrefixSelectionChoiceBox.java new file mode 100644 index 0000000000000000000000000000000000000000..d5cf6ce143dc774e42edd507a1ff34088ce0ccbe --- /dev/null +++ b/src/org/controlsfx/control/PrefixSelectionChoiceBox.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.tools.PrefixSelectionCustomizer; +import javafx.scene.control.ChoiceBox; + +/** + * <p>A simple extension of the {@link ChoiceBox} which selects an entry of + * its item list based on keyboard input. The user can type letters or + * digits on the keyboard and die {@link ChoiceBox} will attempt to + * select the first item it can find with a matching prefix. + * + * <p>This feature is available natively on the Windows combo box control, so many + * users have asked for it. There is a feature request to include this feature + * into JavaFX (<a href="https://javafx-jira.kenai.com/browse/RT-18064">Issue RT-18064</a>). + * The class is published as part of ContorlsFX to allow testing and feedback. + * + * <h3>Example</h3> + * + * <p>Let's look at an example to clarify this. The choice box offers the items + * ["Aaaaa", "Abbbb", "Abccc", "Abcdd", "Abcde"]. The user now types "abc" in + * quick succession (and then stops typing). The choice box will select a new entry + * on every key pressed. The first entry it will select is "Aaaaa" since it is the + * first entry that starts with an "a" (case ignored). It will then select "Abbbb", + * since this is the first entry that started with "ab" and will finally settle for + * "Abccc". + * + * <ul><table> + * <tr><th>Keys typed</th><th>Element selected</th></tr> + * <tr><td>a</td><td>Aaaaa<td></tr> + * <tr><td>aaa</td><td>Aaaaa<td></tr> + * <tr><td>ab</td><td>Abbbb<td></tr> + * <tr><td>abc</td><td>Abccc<td></tr> + * <tr><td>xyz</td><td>-<td></tr> + * </table></ul> + * + * <p>If you want to modify an existing {@link ChoiceBox} you can use the + * {@link PrefixSelectionCustomizer} directly to do this. + * + * @see PrefixSelectionCustomizer + */ +public class PrefixSelectionChoiceBox<T> extends ChoiceBox<T> { + + /** + * Create a non editable {@link ChoiceBox} with the "prefix selection" + * feature installed. + */ + public PrefixSelectionChoiceBox() { + PrefixSelectionCustomizer.customize(this); + } + +} diff --git a/src/org/controlsfx/control/PrefixSelectionComboBox.java b/src/org/controlsfx/control/PrefixSelectionComboBox.java new file mode 100644 index 0000000000000000000000000000000000000000..98092f610fb5b1d88474afc05e98db0b5774e82a --- /dev/null +++ b/src/org/controlsfx/control/PrefixSelectionComboBox.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.tools.PrefixSelectionCustomizer; +import javafx.scene.control.ComboBox; + +/** + * A simple extension of the {@link ComboBox} which selects an entry of + * its item list based on keyboard input. The user can type letters or + * digits on the keyboard and die ChoiceBox will attempt to + * select the first item it can find with a matching prefix. + * + * This will only be enabled, when the {@link ComboBox} is not editable, so + * this class will be setup as non editable by default. + * + * <p>This feature is available natively on the Windows combo box control, so many + * users have asked for it. There is a feature request to include this feature + * into JavaFX (<a href="https://javafx-jira.kenai.com/browse/RT-18064">Issue RT-18064</a>). + * The class is published as part of ContorlsFX to allow testing and feedback. + * + * <h3>Example</h3> + * + * <p>Let's look at an example to clarify this. The combo box offers the items + * ["Aaaaa", "Abbbb", "Abccc", "Abcdd", "Abcde"]. The user now types "abc" in + * quick succession (and then stops typing). The combo box will select a new entry + * on every key pressed. The first entry it will select is "Aaaaa" since it is the + * first entry that starts with an "a" (case ignored). It will then select "Abbbb", + * since this is the first entry that started with "ab" and will finally settle for + * "Abccc". + * + * <ul><table> + * <tr><th>Keys typed</th><th>Element selected</th></tr> + * <tr><td>a</td><td>Aaaaa<td></tr> + * <tr><td>aaa</td><td>Aaaaa<td></tr> + * <tr><td>ab</td><td>Abbbb<td></tr> + * <tr><td>abc</td><td>Abccc<td></tr> + * <tr><td>xyz</td><td>-<td></tr> + * </table></ul> + * + * <p>If you want to modify an existing {@link ComboBox} you can use the + * {@link PrefixSelectionCustomizer} directly to do this. + * + * @see PrefixSelectionCustomizer + */ +public class PrefixSelectionComboBox<T> extends ComboBox<T> { + + /** + * Create a non editable {@link ComboBox} with the "prefix selection" + * feature installed. + */ + public PrefixSelectionComboBox() { + setEditable(false); + PrefixSelectionCustomizer.customize(this); + } + +} diff --git a/src/org/controlsfx/control/PropertySheet.java b/src/org/controlsfx/control/PropertySheet.java new file mode 100644 index 0000000000000000000000000000000000000000..2675b7c24a8dcf57698903899d4cc5a0bf689f38 --- /dev/null +++ b/src/org/controlsfx/control/PropertySheet.java @@ -0,0 +1,452 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.PropertySheetSkin; + +import java.util.Optional; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Skin; +import javafx.util.Callback; + +import org.controlsfx.property.BeanPropertyUtils; +import org.controlsfx.property.editor.DefaultPropertyEditorFactory; +import org.controlsfx.property.editor.Editors; +import org.controlsfx.property.editor.PropertyEditor; + +/** + * The PropertySheet control is a powerful control designed to make it really + * easy for developers to present to end users a list of properties that the + * end user is allowed to manipulate. Commonly a property sheet is used in + * visual editors and other tools where a lot of properties exist. + * + * <p>To better describe what a property sheet is, please refer to the picture + * below: + * + * <br> + * <center><img src="propertySheet.PNG" alt="Screenshot of PropertySheet"></center> + * + * <p>In this property sheet there exists two columns: the left column shows a + * label describing the property itself, whereas the right column provides a + * {@link PropertyEditor} that allows the end user the means to manipulate the + * property. In the screenshot you can see CheckEditor, + * ChoiceEditor, TextEditor and FontEditor, among the + * many editors that are available in the {@link Editors} + * package. + * + * <p>To create a PropertySheet is simple: you firstly instantiate an instance + * of PropertySheet, and then you pass in a list of {@link Item} instances, + * where each Item represents a single property that is to be editable by the + * end user. + * + * <h3>Working with JavaBeans</h3> + * Because a very common use case for a property sheet is editing properties on + * a JavaBean, there is convenience API for making this interaction easier. + * Refer to the {@link BeanPropertyUtils class}, in particular the + * {@link BeanPropertyUtils#getProperties(Object)} method that will return a + * list of Item instances, one Item instance per property on the given JavaBean. + * + * @see Item + * @see Mode + */ +public class PropertySheet extends ControlsFXControl { + + + /************************************************************************** + * + * Static fields + * + **************************************************************************/ + + + + /************************************************************************** + * + * Static enumerations / interfaces + * + **************************************************************************/ + + /** + * Specifies how the {@link PropertySheet} should be laid out. Refer to the + * enumeration values to learn what each one means. + */ + public static enum Mode { + /** + * Simply displays the properties in the + * {@link PropertySheet#getItems() items list} in the order they are + * in the list. + */ + NAME, + + /** + * Groups the properties in the + * {@link PropertySheet#getItems() items list} based on their + * {@link Item#getCategory() category}. + */ + CATEGORY + } + + + + /** + * A wrapper interface for a single property to be displayed in a + * {@link PropertySheet} control. + * + * @see PropertySheet + */ + public static interface Item { + + /** + * Returns the class type of the property. + */ + public Class<?> getType(); + + /** + * Returns a String representation of the category of the property. This + * is relevant when the {@link PropertySheet} + * {@link PropertySheet#modeProperty() mode property} is set to + * {@link Mode#CATEGORY} - as then all properties with the same category + * will be grouped together visually. + */ + public String getCategory(); + + /** + * Returns the display name of the property, which should be short (i.e. + * less than two words). This is used to explain to the end user what the + * property represents and is displayed beside the {@link PropertyEditor}. + * If you need to explain more detail to the user, consider placing it + * in the {@link #getDescription()}. + */ + public String getName(); + + /** + * A String that will be shown to the user as a tooltip. This allows for + * a longer form of detail than what is possible with the {@link #getName()} + * method. + */ + public String getDescription(); + + /** + * Returns the current value of the property. + */ + public Object getValue(); + + /** + * Sets the current value of the property. + */ + public void setValue(Object value); + + /** + * Returns the underlying ObservableValue, where one exists, that the editor + * can monitor for changes. + */ + public Optional<ObservableValue<? extends Object>> getObservableValue(); + + /** + * Returns an Optional wrapping the class of the PropertyEditor that + * should be used for editing this item. The default implementation + * returns Optional.empty() + * + * The class must have a constructor that can accept a single argument + * of type PropertySheet.Item + */ + default public Optional<Class<? extends PropertyEditor<?>>> getPropertyEditorClass() { + return Optional.empty(); + } + + /** + * Indicates whether the PropertySheet should allow editing of this + * property, or whether it is read-only. The default implementation + * returns true. + */ + default public boolean isEditable() { + return true; + } + } + + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private final ObservableList<Item> items; + + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a default PropertySheet instance with no properties to edit. + */ + public PropertySheet() { + this(null); + } + + /** + * Creates a PropertySheet instance prepopulated with the items provided + * in the items {@link ObservableList}. + * + * @param items The items that should appear within the PropertySheet. + */ + public PropertySheet(ObservableList<Item> items) { + getStyleClass().add(DEFAULT_STYLE_CLASS); + + this.items = items == null ? FXCollections.<Item>observableArrayList() : items; + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * + * @return An ObservableList of properties that will be displayed to the user to allow for them + * to be edited. + */ + public ObservableList<Item> getItems() { + return items; + } + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new PropertySheetSkin(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(PropertySheet.class, "propertysheet.css"); + } + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- modeProperty + private final SimpleObjectProperty<Mode> modeProperty = + new SimpleObjectProperty<>(this, "mode", Mode.NAME); //$NON-NLS-1$ + + /** + * Used to represent how the properties should be laid out in + * the PropertySheet. Refer to the {@link Mode} enumeration to better + * understand the available options. + * @return A SimpleObjectproperty. + */ + public final SimpleObjectProperty<Mode> modeProperty() { + return modeProperty; + } + + /** + * @see Mode + * @return how the properties should be laid out in + * the PropertySheet. + */ + public final Mode getMode() { + return modeProperty.get(); + } + + /** + * Set how the properties should be laid out in + * the PropertySheet. + * @param mode + */ + public final void setMode(Mode mode) { + modeProperty.set(mode); + } + + + // --- propertyEditorFactory + private final SimpleObjectProperty<Callback<Item, PropertyEditor<?>>> propertyEditorFactory = + new SimpleObjectProperty<>(this, "propertyEditor", new DefaultPropertyEditorFactory()); //$NON-NLS-1$ + + /** + * The property editor factory is used by the PropertySheet to determine which + * {@link PropertyEditor} to use for a given {@link Item}. By default the + * {@link DefaultPropertyEditorFactory} is used, but this may be replaced + * or extended by developers wishing to add in (or substitute) their own + * property editors. + * + * @return A SimpleObjectproperty. + */ + public final SimpleObjectProperty<Callback<Item, PropertyEditor<?>>> propertyEditorFactory() { + return propertyEditorFactory; + } + + /** + * + * @return The editor factory used by the PropertySheet to determine which + * {@link PropertyEditor} to use for a given {@link Item}. + */ + public final Callback<Item, PropertyEditor<?>> getPropertyEditorFactory() { + return propertyEditorFactory.get(); + } + + /** + * Sets a new editor factory used by the PropertySheet to determine which + * {@link PropertyEditor} to use for a given {@link Item}. + * @param factory + */ + public final void setPropertyEditorFactory( Callback<Item, PropertyEditor<?>> factory ) { + propertyEditorFactory.set( factory == null? new DefaultPropertyEditorFactory(): factory ); + } + + + // --- modeSwitcherVisible + private final SimpleBooleanProperty modeSwitcherVisible = + new SimpleBooleanProperty(this, "modeSwitcherVisible", true); //$NON-NLS-1$ + + /** + * This property represents whether a visual option should be presented to + * users to switch between the various {@link Mode modes} available. By + * default this is true, so setting it to false will hide these buttons. + * @return A SimpleBooleanproperty. + */ + public final SimpleBooleanProperty modeSwitcherVisibleProperty() { + return modeSwitcherVisible; + } + + /** + * + * @return whether a visual option is presented to + * users to switch between the various {@link Mode modes} available. + */ + public final boolean isModeSwitcherVisible() { + return modeSwitcherVisible.get(); + } + + /** + * Set whether a visual option should be presented to + * users to switch between the various {@link Mode modes} available. + * @param visible + */ + public final void setModeSwitcherVisible( boolean visible ) { + modeSwitcherVisible.set(visible); + } + + + // --- toolbarSearchVisibleProperty + private final SimpleBooleanProperty searchBoxVisible = + new SimpleBooleanProperty(this, "searchBoxVisible", true); //$NON-NLS-1$ + + /** + * + */ + /** + * This property represents whether a text field should be presented to + * users to allow for them to filter the properties in the property sheet to + * only show ones matching the typed input. By default this is true, so + * setting it to false will hide this search field. + * @return A SimpleBooleanProperty. + */ + public final SimpleBooleanProperty searchBoxVisibleProperty() { + return searchBoxVisible; + } + + /** + * + * @return whether a text field should be presented to + * users to allow for them to filter the properties in the property sheet to + * only show ones matching the typed input. + */ + public final boolean isSearchBoxVisible() { + return searchBoxVisible.get(); + } + + /** + * Sets whether a text field should be presented to + * users to allow for them to filter the properties in the property sheet to + * only show ones matching the typed input. + * @param visible + */ + public final void setSearchBoxVisible( boolean visible ) { + searchBoxVisible.set(visible); + } + + + // --- titleFilterProperty + private final SimpleStringProperty titleFilterProperty = + new SimpleStringProperty(this, "titleFilter", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + /** + * Regardless of whether the {@link #searchBoxVisibleProperty() search box} + * is visible or not, it is possible to filter the options shown on screen + * using this title filter property. If the search box is visible, it will + * manipulate this property to contain whatever the user types. + * @return A SimpleStringProperty. + */ + public final SimpleStringProperty titleFilter() { + return titleFilterProperty; + } + + /** + * @see #titleFilter() + * @return the filter for filtering the options shown on screen + */ + public final String getTitleFilter() { + return titleFilterProperty.get(); + } + + /** + * Sets the filter for filtering the options shown on screen. + * @param filter + * @see #titleFilter() + */ + public final void setTitleFilter( String filter ) { + titleFilterProperty.set(filter); + } + + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "property-sheet"; //$NON-NLS-1$ + +} diff --git a/src/org/controlsfx/control/RangeSlider.java b/src/org/controlsfx/control/RangeSlider.java new file mode 100644 index 0000000000000000000000000000000000000000..b60462ac3847b4751730775762a16be649a6e96d --- /dev/null +++ b/src/org/controlsfx/control/RangeSlider.java @@ -0,0 +1,1102 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.RangeSliderSkin; +import org.controlsfx.tools.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.DoublePropertyBase; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.css.CssMetaData; +import javafx.css.PseudoClass; +import javafx.css.StyleOrigin; +import javafx.css.Styleable; +import javafx.css.StyleableBooleanProperty; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableIntegerProperty; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.geometry.Orientation; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.control.Slider; + +import com.sun.javafx.css.converters.BooleanConverter; +import com.sun.javafx.css.converters.EnumConverter; +import com.sun.javafx.css.converters.SizeConverter; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.util.StringConverter; + +/** + * The RangeSlider control is simply a JavaFX {@link Slider} control with support + * for two 'thumbs', rather than one. A thumb is the non-technical name for the + * draggable area inside the Slider / RangeSlider that allows for a value to be + * set. + * + * <p>Because the RangeSlider has two thumbs, it also has a few additional rules + * and user interactions: + * + * <ol> + * <li>The 'lower value' thumb can not move past the 'higher value' thumb. + * <li>Whereas the {@link Slider} control only has one + * {@link Slider#valueProperty() value} property, the RangeSlider has a + * {@link #lowValueProperty() low value} and a + * {@link #highValueProperty() high value} property, not surprisingly + * represented by the 'low value' and 'high value' thumbs. + * <li>The area between the low and high values represents the allowable range. + * For example, if the low value is 2 and the high value is 8, then the + * allowable range is between 2 and 8. + * <li>The allowable range area is rendered differently. This area is able to + * be dragged with mouse / touch input to allow for the entire range to + * be modified. For example, following on from the previous example of the + * allowable range being between 2 and 8, if the user drags the range bar + * to the right, the low value will adjust to 3, and the high value 9, and + * so on until the user stops adjusting. + * </ol> + * + * <h3>Screenshots</h3> + * Because the RangeSlider supports both horizontal and vertical + * {@link #orientationProperty() orientation}, there are two screenshots below: + * + * <table border="0" summary="Screenshot of RangeSlider orientation"> + * <tr> + * <td width="75" valign="center"><strong>Horizontal:</strong></td> + * <td><img src="rangeSlider-horizontal.png" alt="Screenshot of a horizontal RangeSlider"></td> + * </tr> + * <tr> + * <td width="75" valign="top"><strong>Vertical:</strong></td> + * <td><img src="rangeSlider-vertical.png" alt="Screenshot of a vertical RangeSlider"></td> + * </tr> + * </table> + * + * <h3>Code Samples</h3> + * Instantiating a RangeSlider is simple. The first decision is to decide whether + * a horizontal or a vertical track is more appropriate. By default RangeSlider + * instances are horizontal, but this can be changed by setting the + * {@link #orientationProperty() orientation} property. + * + * <p>Once the orientation is determined, the next most important decision is + * to determine what the {@link #minProperty() min} / {@link #maxProperty() max} + * and default {@link #lowValueProperty() low} / {@link #highValueProperty() high} + * values are. The min / max values represent the smallest and largest legal + * values for the thumbs to be set to, whereas the low / high values represent + * where the thumbs are currently, within the bounds of the min / max values. + * Because all four values are required in all circumstances, they are all + * required parameters to instantiate a RangeSlider: the constructor takes + * four doubles, representing min, max, lowValue and highValue (in that order). + * + * <p>For example, here is a simple horizontal RangeSlider that has a minimum + * value of 0, a maximum value of 100, a low value of 10 and a high value of 90: + * + * <pre>{@code final RangeSlider hSlider = new RangeSlider(0, 100, 10, 90);}</pre> + * + * <p>To configure the hSlider to look like the RangeSlider in the horizontal + * RangeSlider screenshot above only requires a few additional properties to be + * set: + * + * <pre> + * {@code + * final RangeSlider hSlider = new RangeSlider(0, 100, 10, 90); + * hSlider.setShowTickMarks(true); + * hSlider.setShowTickLabels(true); + * hSlider.setBlockIncrement(10);}</pre> + * + * <p>To create a vertical slider, simply do the following: + * + * <pre> + * {@code + * final RangeSlider vSlider = new RangeSlider(0, 200, 30, 150); + * vSlider.setOrientation(Orientation.VERTICAL);}</pre> + * + * <p>This code creates a RangeSlider with a min value of 0, a max value of 200, + * a low value of 30, and a high value of 150. + * + * @see Slider + */ +public class RangeSlider extends ControlsFXControl { + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Creates a new RangeSlider instance using default values of 0.0, 0.25, 0.75 + * and 1.0 for min/lowValue/highValue/max, respectively. + */ + public RangeSlider() { + this(0, 1.0, 0.25, 0.75); + } + + /** + * Instantiates a default, horizontal RangeSlider with the specified + * min/max/low/high values. + * + * @param min The minimum allowable value that the RangeSlider will allow. + * @param max The maximum allowable value that the RangeSlider will allow. + * @param lowValue The initial value for the low value in the RangeSlider. + * @param highValue The initial value for the high value in the RangeSlider. + */ + public RangeSlider(double min, double max, double lowValue, double highValue) { + getStyleClass().setAll(DEFAULT_STYLE_CLASS); + + setMax(max); + setMin(min); + adjustValues(); + setLowValue(lowValue); + setHighValue(highValue); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(RangeSlider.class, "rangeslider.css"); + } + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new RangeSliderSkin(this); + } + + + + /*************************************************************************** + * * + * New properties (over and above what is in Slider) * + * * + **************************************************************************/ + + // --- low value + /** + * The low value property represents the current position of the low value + * thumb, and is within the allowable range as specified by the + * {@link #minProperty() min} and {@link #maxProperty() max} properties. By + * default this value is 0. + */ + public final DoubleProperty lowValueProperty() { + return lowValue; + } + private DoubleProperty lowValue = new SimpleDoubleProperty(this, "lowValue", 0.0D) { //$NON-NLS-1$ + @Override protected void invalidated() { + adjustLowValues(); + } + }; + + /** + * Sets the low value for the range slider, which may or may not be clamped + * to be within the allowable range as specified by the + * {@link #minProperty() min} and {@link #maxProperty() max} properties. + */ + public final void setLowValue(double d) { + lowValueProperty().set(d); + } + + /** + * Returns the current low value for the range slider. + */ + public final double getLowValue() { + return lowValue != null ? lowValue.get() : 0.0D; + } + + + + // --- low value changing + /** + * When true, indicates the current low value of this RangeSlider is changing. + * It provides notification that the low value is changing. Once the low + * value is computed, it is set back to false. + */ + public final BooleanProperty lowValueChangingProperty() { + if (lowValueChanging == null) { + lowValueChanging = new SimpleBooleanProperty(this, "lowValueChanging", false); //$NON-NLS-1$ + } + return lowValueChanging; + } + + private BooleanProperty lowValueChanging; + + /** + * Call this when the low value is changing. + * @param value True if the low value is changing, false otherwise. + */ + public final void setLowValueChanging(boolean value) { + lowValueChangingProperty().set(value); + } + + /** + * Returns whether or not the low value of this RangeSlider is currently + * changing. + */ + public final boolean isLowValueChanging() { + return lowValueChanging == null ? false : lowValueChanging.get(); + } + + + // --- high value + /** + * The high value property represents the current position of the high value + * thumb, and is within the allowable range as specified by the + * {@link #minProperty() min} and {@link #maxProperty() max} properties. By + * default this value is 100. + */ + public final DoubleProperty highValueProperty() { + return highValue; + } + private DoubleProperty highValue = new SimpleDoubleProperty(this, "highValue", 100D) { //$NON-NLS-1$ + @Override protected void invalidated() { + adjustHighValues(); + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "highValue"; //$NON-NLS-1$ + } + }; + + /** + * Sets the high value for the range slider, which may or may not be clamped + * to be within the allowable range as specified by the + * {@link #minProperty() min} and {@link #maxProperty() max} properties. + */ + public final void setHighValue(double d) { + if (!highValueProperty().isBound()) highValueProperty().set(d); + } + + /** + * Returns the current high value for the range slider. + */ + public final double getHighValue() { + return highValue != null ? highValue.get() : 100D; + } + + + + // --- high value changing + /** + * When true, indicates the current high value of this RangeSlider is changing. + * It provides notification that the high value is changing. Once the high + * value is computed, it is set back to false. + */ + public final BooleanProperty highValueChangingProperty() { + if (highValueChanging == null) { + highValueChanging = new SimpleBooleanProperty(this, "highValueChanging", false); //$NON-NLS-1$ + } + return highValueChanging; + } + private BooleanProperty highValueChanging; + + /** + * Call this when high low value is changing. + * @param value True if the high value is changing, false otherwise. + */ + public final void setHighValueChanging(boolean value) { + highValueChangingProperty().set(value); + } + + /** + * Returns whether or not the high value of this RangeSlider is currently + * changing. + */ + public final boolean isHighValueChanging() { + return highValueChanging == null ? false : highValueChanging.get(); + } + + private final ObjectProperty<StringConverter<Number>> tickLabelFormatter = new SimpleObjectProperty<>(); + + /** + * Gets the value of the property tickLabelFormatter. + * @return the value of the property tickLabelFormatter. + */ + public final StringConverter<Number> getLabelFormatter(){ + return tickLabelFormatter.get(); + } + + /** + * Sets the value of the property tickLabelFormatter. + * @param value + */ + public final void setLabelFormatter(StringConverter<Number> value){ + tickLabelFormatter.set(value); + } + /** + * StringConverter used to format tick mark labels. If null a default will be used. + * @return a Property containing the StringConverter. + */ + public final ObjectProperty<StringConverter<Number>> labelFormatterProperty(){ + return tickLabelFormatter; + } + + /*************************************************************************** + * * + * New public API * + * * + **************************************************************************/ + + /** + * Increments the {@link #lowValueProperty() low value} by the + * {@link #blockIncrementProperty() block increment} amount. + */ + public void incrementLowValue() { + adjustLowValue(getLowValue() + getBlockIncrement()); + } + + /** + * Decrements the {@link #lowValueProperty() low value} by the + * {@link #blockIncrementProperty() block increment} amount. + */ + public void decrementLowValue() { + adjustLowValue(getLowValue() - getBlockIncrement()); + } + + /** + * Increments the {@link #highValueProperty() high value} by the + * {@link #blockIncrementProperty() block increment} amount. + */ + public void incrementHighValue() { + adjustHighValue(getHighValue() + getBlockIncrement()); + } + + /** + * Decrements the {@link #highValueProperty() high value} by the + * {@link #blockIncrementProperty() block increment} amount. + */ + public void decrementHighValue() { + adjustHighValue(getHighValue() - getBlockIncrement()); + } + + /** + * Adjusts {@link #lowValueProperty() lowValue} to match <code>newValue</code>, + * or as closely as possible within the constraints imposed by the + * {@link #minProperty() min} and {@link #maxProperty() max} properties. + * This function also takes into account + * {@link #snapToTicksProperty() snapToTicks}, which is the main difference + * between <code>adjustLowValue</code> and + * {@link #setLowValue(double) setLowValue}. + */ + public void adjustLowValue(double newValue) { + double d1 = getMin(); + double d2 = getMax(); + if (d2 <= d1) { + // no-op + } else { + newValue = newValue >= d1 ? newValue : d1; + newValue = newValue <= d2 ? newValue : d2; + setLowValue(snapValueToTicks(newValue)); + } + } + + /** + * Adjusts {@link #highValueProperty() highValue} to match <code>newValue</code>, + * or as closely as possible within the constraints imposed by the + * {@link #minProperty() min} and {@link #maxProperty() max} properties. + * This function also takes into account + * {@link #snapToTicksProperty() snapToTicks}, which is the main difference + * between <code>adjustHighValue</code> and + * {@link #setHighValue(double) setHighValue}. + */ + public void adjustHighValue(double newValue) { + double d1 = getMin(); + double d2 = getMax(); + if (d2 <= d1) { + // no-op + } else { + newValue = newValue >= d1 ? newValue : d1; + newValue = newValue <= d2 ? newValue : d2; + setHighValue(snapValueToTicks(newValue)); + } + } + + + + /*************************************************************************** + * * + * Properties copied from Slider (and slightly edited) * + * * + **************************************************************************/ + + private DoubleProperty max; + + /** + * Sets the maximum value for this Slider. + * @param value + */ + public final void setMax(double value) { + maxProperty().set(value); + } + + /** + * @return The maximum value of this slider. 100 is returned if + * the maximum value has never been set. + */ + public final double getMax() { + return max == null ? 100 : max.get(); + } + + /** + * + * @return A DoubleProperty representing the maximum value of this Slider. + * This must be a value greater than {@link #minProperty() min}. + */ + public final DoubleProperty maxProperty() { + if (max == null) { + max = new DoublePropertyBase(100) { + @Override protected void invalidated() { + if (get() < getMin()) { + setMin(get()); + } + adjustValues(); + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "max"; //$NON-NLS-1$ + } + }; + } + return max; + } + + private DoubleProperty min; + + /** + * Sets the minimum value for this Slider. + * @param value + */ + public final void setMin(double value) { + minProperty().set(value); + } + + /** + * + * @return the minimum value for this Slider. 0 is returned if the minimum + * has never been set. + */ + public final double getMin() { + return min == null ? 0 : min.get(); + } + + /** + * + * @return A DoubleProperty representing The minimum value of this Slider. + * This must be a value less than {@link #maxProperty() max}. + */ + public final DoubleProperty minProperty() { + if (min == null) { + min = new DoublePropertyBase(0) { + @Override protected void invalidated() { + if (get() > getMax()) { + setMax(get()); + } + adjustValues(); + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "min"; //$NON-NLS-1$ + } + }; + } + return min; + } + + /** + * + */ + private BooleanProperty snapToTicks; + + /** + * Sets the value of SnapToTicks. + * @see #snapToTicksProperty() + * @param value + */ + public final void setSnapToTicks(boolean value) { + snapToTicksProperty().set(value); + } + + /** + * + * @return the value of SnapToTicks. + * @see #snapToTicksProperty() + */ + public final boolean isSnapToTicks() { + return snapToTicks == null ? false : snapToTicks.get(); + } + + /** + * Indicates whether the {@link #lowValueProperty()} value} / + * {@link #highValueProperty()} value} of the {@code Slider} should always + * be aligned with the tick marks. This is honored even if the tick marks + * are not shown. + * @return A BooleanProperty. + */ + public final BooleanProperty snapToTicksProperty() { + if (snapToTicks == null) { + snapToTicks = new StyleableBooleanProperty(false) { + @Override public CssMetaData<? extends Styleable, Boolean> getCssMetaData() { + return RangeSlider.StyleableProperties.SNAP_TO_TICKS; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "snapToTicks"; //$NON-NLS-1$ + } + }; + } + return snapToTicks; + } + /** + * + */ + private DoubleProperty majorTickUnit; + + /** + * Sets the unit distance between major tick marks. + * @param value + * @see #majorTickUnitProperty() + */ + public final void setMajorTickUnit(double value) { + if (value <= 0) { + throw new IllegalArgumentException("MajorTickUnit cannot be less than or equal to 0."); //$NON-NLS-1$ + } + majorTickUnitProperty().set(value); + } + + /** + * @see #majorTickUnitProperty() + * @return The unit distance between major tick marks. + */ + public final double getMajorTickUnit() { + return majorTickUnit == null ? 25 : majorTickUnit.get(); + } + + /** + * The unit distance between major tick marks. For example, if + * the {@link #minProperty() min} is 0 and the {@link #maxProperty() max} is 100 and the + * {@link #majorTickUnitProperty() majorTickUnit} is 25, then there would be 5 tick marks: one at + * position 0, one at position 25, one at position 50, one at position + * 75, and a final one at position 100. + * <p> + * This value should be positive and should be a value less than the + * span. Out of range values are essentially the same as disabling + * tick marks. + * + * @return A DoubleProperty + */ + public final DoubleProperty majorTickUnitProperty() { + if (majorTickUnit == null) { + majorTickUnit = new StyleableDoubleProperty(25) { + @Override public void invalidated() { + if (get() <= 0) { + throw new IllegalArgumentException("MajorTickUnit cannot be less than or equal to 0."); //$NON-NLS-1$ + } + } + + @Override public CssMetaData<? extends Styleable, Number> getCssMetaData() { + return StyleableProperties.MAJOR_TICK_UNIT; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "majorTickUnit"; //$NON-NLS-1$ + } + }; + } + return majorTickUnit; + } + /** + * + */ + private IntegerProperty minorTickCount; + + /** + * Sets the number of minor ticks to place between any two major ticks. + * @param value + * @see #minorTickCountProperty() + */ + public final void setMinorTickCount(int value) { + minorTickCountProperty().set(value); + } + + /** + * @see #minorTickCountProperty() + * @return The number of minor ticks to place between any two major ticks. + */ + public final int getMinorTickCount() { + return minorTickCount == null ? 3 : minorTickCount.get(); + } + + /** + * The number of minor ticks to place between any two major ticks. This + * number should be positive or zero. Out of range values will disable + * disable minor ticks, as will a value of zero. + * @return An InterProperty + */ + public final IntegerProperty minorTickCountProperty() { + if (minorTickCount == null) { + minorTickCount = new StyleableIntegerProperty(3) { + @Override public CssMetaData<? extends Styleable, Number> getCssMetaData() { + return RangeSlider.StyleableProperties.MINOR_TICK_COUNT; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "minorTickCount"; //$NON-NLS-1$ + } + }; + } + return minorTickCount; + } + /** + * + */ + private DoubleProperty blockIncrement; + + /** + * Sets the amount by which to adjust the slider if the track of the slider is + * clicked. + * @param value + * @see #blockIncrementProperty() + */ + public final void setBlockIncrement(double value) { + blockIncrementProperty().set(value); + } + + /** + * @see #blockIncrementProperty() + * @return The amount by which to adjust the slider if the track of the slider is + * clicked. + */ + public final double getBlockIncrement() { + return blockIncrement == null ? 10 : blockIncrement.get(); + } + + /** + * The amount by which to adjust the slider if the track of the slider is + * clicked. This is used when manipulating the slider position using keys. If + * {@link #snapToTicksProperty() snapToTicks} is true then the nearest tick mark to the adjusted + * value will be used. + * @return A DoubleProperty + */ + public final DoubleProperty blockIncrementProperty() { + if (blockIncrement == null) { + blockIncrement = new StyleableDoubleProperty(10) { + @Override public CssMetaData<? extends Styleable, Number> getCssMetaData() { + return RangeSlider.StyleableProperties.BLOCK_INCREMENT; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "blockIncrement"; //$NON-NLS-1$ + } + }; + } + return blockIncrement; + } + + /** + * + */ + private ObjectProperty<Orientation> orientation; + + /** + * Sets the orientation of the Slider. + * @param value + */ + public final void setOrientation(Orientation value) { + orientationProperty().set(value); + } + + /** + * + * @return The orientation of the Slider. {@link Orientation#HORIZONTAL} is + * returned by default. + */ + public final Orientation getOrientation() { + return orientation == null ? Orientation.HORIZONTAL : orientation.get(); + } + + /** + * The orientation of the {@code Slider} can either be horizontal + * or vertical. + * @return An Objectproperty representing the orientation of the Slider. + */ + public final ObjectProperty<Orientation> orientationProperty() { + if (orientation == null) { + orientation = new StyleableObjectProperty<Orientation>(Orientation.HORIZONTAL) { + @Override protected void invalidated() { + final boolean vertical = (get() == Orientation.VERTICAL); + pseudoClassStateChanged(VERTICAL_PSEUDOCLASS_STATE, vertical); + pseudoClassStateChanged(HORIZONTAL_PSEUDOCLASS_STATE, ! vertical); + } + + @Override public CssMetaData<? extends Styleable, Orientation> getCssMetaData() { + return RangeSlider.StyleableProperties.ORIENTATION; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "orientation"; //$NON-NLS-1$ + } + }; + } + return orientation; + } + + private BooleanProperty showTickLabels; + + /** + * Sets whether labels of tick marks should be shown or not. + * @param value + */ + public final void setShowTickLabels(boolean value) { + showTickLabelsProperty().set(value); + } + + /** + * @return whether labels of tick marks are being shown. + */ + public final boolean isShowTickLabels() { + return showTickLabels == null ? false : showTickLabels.get(); + } + + /** + * Indicates that the labels for tick marks should be shown. Typically a + * {@link Skin} implementation will only show labels if + * {@link #showTickMarksProperty() showTickMarks} is also true. + * @return A BooleanProperty + */ + public final BooleanProperty showTickLabelsProperty() { + if (showTickLabels == null) { + showTickLabels = new StyleableBooleanProperty(false) { + @Override public CssMetaData<? extends Styleable, Boolean> getCssMetaData() { + return RangeSlider.StyleableProperties.SHOW_TICK_LABELS; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "showTickLabels"; //$NON-NLS-1$ + } + }; + } + return showTickLabels; + } + /** + * + */ + private BooleanProperty showTickMarks; + + /** + * Specifies whether the {@link Skin} implementation should show tick marks. + * @param value + */ + public final void setShowTickMarks(boolean value) { + showTickMarksProperty().set(value); + } + + /** + * + * @return whether the {@link Skin} implementation should show tick marks. + */ + public final boolean isShowTickMarks() { + return showTickMarks == null ? false : showTickMarks.get(); + } + + /** + * @return A BooleanProperty that specifies whether the {@link Skin} + * implementation should show tick marks. + */ + public final BooleanProperty showTickMarksProperty() { + if (showTickMarks == null) { + showTickMarks = new StyleableBooleanProperty(false) { + @Override public CssMetaData<? extends Styleable, Boolean> getCssMetaData() { + return RangeSlider.StyleableProperties.SHOW_TICK_MARKS; + } + + @Override public Object getBean() { + return RangeSlider.this; + } + + @Override public String getName() { + return "showTickMarks"; //$NON-NLS-1$ + } + }; + } + return showTickMarks; + } + + + + /*************************************************************************** + * * + * Private methods * + * * + **************************************************************************/ + + /** + * Ensures that min is always < max, that value is always + * somewhere between the two, and that if snapToTicks is set then the + * value will always be set to align with a tick mark. + */ + private void adjustValues() { + adjustLowValues(); + adjustHighValues(); + } + + private void adjustLowValues() { + /** + * We first look if the LowValue is between the min and max. + */ + if (getLowValue() < getMin() || getLowValue() > getMax()) { + double value = Utils.clamp(getMin(), getLowValue(), getMax()); + setLowValue(value); + /** + * If the LowValue seems right, we check if it's not superior to + * HighValue ONLY if the highValue itself is right. Because it may + * happen that the highValue has not yet been computed and is + * wrong, and therefore force the lowValue to change in a wrong way + * which may end up in an infinite loop. + */ + } else if (getLowValue() >= getHighValue() && (getHighValue() >= getMin() && getHighValue() <= getMax())) { + double value = Utils.clamp(getMin(), getLowValue(), getHighValue()); + setLowValue(value); + } + } + + private double snapValueToTicks(double d) { + double d1 = d; + if (isSnapToTicks()) { + double d2 = 0.0D; + if (getMinorTickCount() != 0) { + d2 = getMajorTickUnit() / (double) (Math.max(getMinorTickCount(), 0) + 1); + } else { + d2 = getMajorTickUnit(); + } + int i = (int) ((d1 - getMin()) / d2); + double d3 = (double) i * d2 + getMin(); + double d4 = (double) (i + 1) * d2 + getMin(); + d1 = Utils.nearest(d3, d1, d4); + } + return Utils.clamp(getMin(), d1, getMax()); + } + + private void adjustHighValues() { + if (getHighValue() < getMin() || getHighValue() > getMax()) { + setHighValue(Utils.clamp(getMin(), getHighValue(), getMax())); + } else if (getHighValue() < getLowValue() && (getLowValue() >= getMin() && getLowValue() <= getMax())) { + setHighValue(Utils.clamp(getLowValue(), getHighValue(), getMax())); + } + } + + + + /************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "range-slider"; //$NON-NLS-1$ + + private static class StyleableProperties { + private static final CssMetaData<RangeSlider,Number> BLOCK_INCREMENT = + new CssMetaData<RangeSlider,Number>("-fx-block-increment", //$NON-NLS-1$ + SizeConverter.getInstance(), 10.0) { + + @Override public boolean isSettable(RangeSlider n) { + return n.blockIncrement == null || !n.blockIncrement.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Number> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Number>)n.blockIncrementProperty(); + } + }; + + private static final CssMetaData<RangeSlider,Boolean> SHOW_TICK_LABELS = + new CssMetaData<RangeSlider,Boolean>("-fx-show-tick-labels", //$NON-NLS-1$ + BooleanConverter.getInstance(), Boolean.FALSE) { + + @Override public boolean isSettable(RangeSlider n) { + return n.showTickLabels == null || !n.showTickLabels.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Boolean> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Boolean>)n.showTickLabelsProperty(); + } + }; + + private static final CssMetaData<RangeSlider,Boolean> SHOW_TICK_MARKS = + new CssMetaData<RangeSlider,Boolean>("-fx-show-tick-marks", //$NON-NLS-1$ + BooleanConverter.getInstance(), Boolean.FALSE) { + + @Override public boolean isSettable(RangeSlider n) { + return n.showTickMarks == null || !n.showTickMarks.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Boolean> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Boolean>)n.showTickMarksProperty(); + } + }; + + private static final CssMetaData<RangeSlider,Boolean> SNAP_TO_TICKS = + new CssMetaData<RangeSlider,Boolean>("-fx-snap-to-ticks", //$NON-NLS-1$ + BooleanConverter.getInstance(), Boolean.FALSE) { + + @Override public boolean isSettable(RangeSlider n) { + return n.snapToTicks == null || !n.snapToTicks.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Boolean> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Boolean>)n.snapToTicksProperty(); + } + }; + + private static final CssMetaData<RangeSlider,Number> MAJOR_TICK_UNIT = + new CssMetaData<RangeSlider,Number>("-fx-major-tick-unit", //$NON-NLS-1$ + SizeConverter.getInstance(), 25.0) { + + @Override public boolean isSettable(RangeSlider n) { + return n.majorTickUnit == null || !n.majorTickUnit.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Number> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Number>)n.majorTickUnitProperty(); + } + }; + + private static final CssMetaData<RangeSlider,Number> MINOR_TICK_COUNT = + new CssMetaData<RangeSlider,Number>("-fx-minor-tick-count", //$NON-NLS-1$ + SizeConverter.getInstance(), 3.0) { + + @SuppressWarnings("deprecation") + @Override public void set(RangeSlider node, Number value, StyleOrigin origin) { + super.set(node, value.intValue(), origin); + } + + @Override public boolean isSettable(RangeSlider n) { + return n.minorTickCount == null || !n.minorTickCount.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Number> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Number>)n.minorTickCountProperty(); + } + }; + + private static final CssMetaData<RangeSlider,Orientation> ORIENTATION = + new CssMetaData<RangeSlider,Orientation>("-fx-orientation", //$NON-NLS-1$ + new EnumConverter<>(Orientation.class), + Orientation.HORIZONTAL) { + + @Override public Orientation getInitialValue(RangeSlider node) { + // A vertical Slider should remain vertical + return node.getOrientation(); + } + + @Override public boolean isSettable(RangeSlider n) { + return n.orientation == null || !n.orientation.isBound(); + } + + @SuppressWarnings("unchecked") + @Override public StyleableProperty<Orientation> getStyleableProperty(RangeSlider n) { + return (StyleableProperty<Orientation>)n.orientationProperty(); + } + }; + + private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; + static { + final List<CssMetaData<? extends Styleable, ?>> styleables = + new ArrayList<>(Control.getClassCssMetaData()); + styleables.add(BLOCK_INCREMENT); + styleables.add(SHOW_TICK_LABELS); + styleables.add(SHOW_TICK_MARKS); + styleables.add(SNAP_TO_TICKS); + styleables.add(MAJOR_TICK_UNIT); + styleables.add(MINOR_TICK_COUNT); + styleables.add(ORIENTATION); + + STYLEABLES = Collections.unmodifiableList(styleables); + } + } + + + /** + * @return The CssMetaData associated with this class, which may include the + * CssMetaData of its super classes. + */ + public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { + return StyleableProperties.STYLEABLES; + } + + private static final PseudoClass VERTICAL_PSEUDOCLASS_STATE = + PseudoClass.getPseudoClass("vertical"); //$NON-NLS-1$ + private static final PseudoClass HORIZONTAL_PSEUDOCLASS_STATE = + PseudoClass.getPseudoClass("horizontal"); //$NON-NLS-1$ +} diff --git a/src/org/controlsfx/control/Rating.java b/src/org/controlsfx/control/Rating.java new file mode 100644 index 0000000000000000000000000000000000000000..a5f1f34aead1fdcd79a77a10f995cfaeaf0df6f0 --- /dev/null +++ b/src/org/controlsfx/control/Rating.java @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.RatingSkin; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.geometry.Orientation; +import javafx.scene.control.Skin; + +/** + * A control for allowing users to provide a rating. This control supports + * {@link #partialRatingProperty() partial ratings} (i.e. not whole numbers and + * dependent upon where the user clicks in the control) and + * {@link #updateOnHoverProperty() updating the rating on hover}. Read on for + * more examples! + * + * <h3>Examples</h3> + * It can be hard to appreciate some of the features of the Rating control, so + * hopefully the following screenshots will help. Firstly, here is what the + * standard (horizontal) Rating control looks like when it has five stars, and a + * rating of two: + * + * <br> + * <center> + * <img src="rating-horizontal.png" alt="Screenshot of horizontal Rating"> + * </center> + * + * <p>To create a Rating control that looks like this is simple: + * + * <pre> + * {@code + * final Rating rating = new Rating();}</pre> + * + * <p>This creates a default horizontal rating control. To create a vertical + * Rating control, simply change the orientation, as such: + * + * <pre> + * {@code + * final Rating rating = new Rating(); + * rating.setOrientation(Orientation.VERTICAL);}</pre> + * + * <p>The end result of making this one line change is shown in the screenshot + * below: + * + * <br> + * <center> + * <img src="rating-vertical.png" alt="Screenshot of vertical Rating"> + * </center> + * + * <p>One of the features of the Rating control is that it doesn't just allow + * for 'integer' ratings: it also allows for the user to click anywhere within + * the rating area to set a 'float' rating. This is hard to describe, but easy + * to show in a picture: + * + * <br> + * <center> + * <img src="rating-partial.png" alt="Screenshot of partial Rating"> + * </center> + * + * <p>In essence, in the screenshot above, the user clicked roughly in the + * middle of the third star. This results in a rating of approximately 2.44. + * To enable {@link #partialRatingProperty() partial ratings}, simply do the + * following when instantiating the Rating control: + * + * <pre> + * {@code + * final Rating rating = new Rating(); + * rating.setPartialRating(true);}</pre> + * + * <p>So far all of the Rating controls demonstrated above have + * required the user to click on the stars to register their rating. This may not + * be the preferred user interaction - often times the preferred approach is to + * simply allow for the rating to be registered by the user hovering their mouse + * over the rating stars. This mode is also supported by the Rating control, + * using the {@link #updateOnHoverProperty() update on hover} property, as such: + * + * <pre> + * {@code + * final Rating rating = new Rating(); + * rating.setUpdateOnHover(true);}</pre> + * + * <p>It is also allowable to have a Rating control that both updates on hover + * and allows for partial values: the 'golden fill' of the default graphics will + * automatically follow the users mouse as they move it along the Rating scale. + * To enable this, just set both properties to true. + */ +public class Rating extends ControlsFXControl { + + /*************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates a default instance with a minimum rating of 0 and a maximum + * rating of 5. + */ + public Rating() { + this(5); + } + + /** + * Creates a default instance with a minimum rating of 0 and a maximum rating + * as provided by the argument. + * + * @param max The maximum allowed rating value. + */ + public Rating(int max) { + this(max, -1); + } + + /** + * Creates a Rating instance with a minimum rating of 0, a maximum rating + * as provided by the {@code max} argument, and a current rating as provided + * by the {@code rating} argument. + * + * @param max The maximum allowed rating value. + */ + public Rating(int max, int rating) { + getStyleClass().setAll("rating"); //$NON-NLS-1$ + + setMax(max); + setRating(rating == -1 ? (int) Math.floor(max / 2.0) : rating); + } + + + + /*************************************************************************** + * + * Overriding public API + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new RatingSkin(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(Rating.class, "rating.css"); + } + + /*************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- Rating + /** + * The current rating value. + */ + public final DoubleProperty ratingProperty() { + return rating; + } + private DoubleProperty rating = new SimpleDoubleProperty(this, "rating", 3); //$NON-NLS-1$ + + /** + * Sets the current rating value. + */ + public final void setRating(double value) { + ratingProperty().set(value); + } + + /** + * Returns the current rating value. + */ + public final double getRating() { + return rating == null ? 3 : rating.get(); + } + + + // --- Max + /** + * The maximum-allowed rating value. + */ + public final IntegerProperty maxProperty() { + return max; + } + private IntegerProperty max = new SimpleIntegerProperty(this, "max", 5); //$NON-NLS-1$ + + /** + * Sets the maximum-allowed rating value. + */ + public final void setMax(int value) { + maxProperty().set(value); + } + + /** + * Returns the maximum-allowed rating value. + */ + public final int getMax() { + return max == null ? 5 : max.get(); + } + + + // --- Orientation + /** + * The {@link Orientation} of the {@code Rating} - this can either be + * horizontal or vertical. + */ + public final ObjectProperty<Orientation> orientationProperty() { + if (orientation == null) { + orientation = new SimpleObjectProperty<>(this, "orientation", Orientation.HORIZONTAL); //$NON-NLS-1$ + } + return orientation; + } + private ObjectProperty<Orientation> orientation; + + /** + * Sets the {@link Orientation} of the {@code Rating} - this can either be + * horizontal or vertical. + */ + public final void setOrientation(Orientation value) { + orientationProperty().set(value); + }; + + /** + * Returns the {@link Orientation} of the {@code Rating} - this can either + * be horizontal or vertical. + */ + public final Orientation getOrientation() { + return orientation == null ? Orientation.HORIZONTAL : orientation.get(); + } + + + // --- partial rating + /** + * If true this allows for users to set a rating as a floating point value. + * In other words, the range of the rating 'stars' can be thought of as a + * range between [0, max], and whereever the user clicks will be calculated + * as the new rating value. If this is false the more typical approach is used + * where the selected 'star' is used as the rating. + */ + public final BooleanProperty partialRatingProperty() { + return partialRating; + } + private BooleanProperty partialRating = new SimpleBooleanProperty(this, "partialRating", false); //$NON-NLS-1$ + + /** + * Sets whether {@link #partialRatingProperty() partial rating} support is + * enabled or not. + */ + public final void setPartialRating(boolean value) { + partialRatingProperty().set(value); + } + + /** + * Returns whether {@link #partialRatingProperty() partial rating} support is + * enabled or not. + */ + public final boolean isPartialRating() { + return partialRating == null ? false : partialRating.get(); + } + + + // --- update on hover + /** + * If true this allows for the {@link #ratingProperty() rating property} to + * be updated simply by the user hovering their mouse over the control. If + * false the user is required to click on their preferred rating to register + * the new rating with this control. + */ + public final BooleanProperty updateOnHoverProperty() { + return updateOnHover; + } + private BooleanProperty updateOnHover = new SimpleBooleanProperty(this, "updateOnHover", false); //$NON-NLS-1$ + + /** + * Sets whether {@link #updateOnHoverProperty() update on hover} support is + * enabled or not. + */ + public final void setUpdateOnHover(boolean value) { + updateOnHoverProperty().set(value); + } + + /** + * Returns whether {@link #updateOnHoverProperty() update on hover} support is + * enabled or not. + */ + public final boolean isUpdateOnHover() { + return updateOnHover == null ? false : updateOnHover.get(); + } +} diff --git a/src/org/controlsfx/control/SegmentedButton.java b/src/org/controlsfx/control/SegmentedButton.java new file mode 100644 index 0000000000000000000000000000000000000000..b27f81a155efb40654b79ade5d1fca0e9bb2c97e --- /dev/null +++ b/src/org/controlsfx/control/SegmentedButton.java @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.SegmentedButtonSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.Skin; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; + +/** + * The SegmentedButton is a simple control that forces together a group of + * {@link ToggleButton} instances such that they appear as one collective button + * (with sub-buttons), rather than as individual buttons. This is better + * clarified with a picture: + * + * <br> + * <center> + * <img src="segmentedButton.png" alt="Screenshot of SegmentedButton"> + * </center> + * + * <h3>Code Samples</h3> + * + * <p>There is very little API on this control, you essentially create + * {@link ToggleButton} instances as per usual (and don't bother putting them + * into a {@link ToggleGroup}, as this is done by the SegmentedButton itself), and then + * you place these buttons inside the {@link #getButtons() buttons list}. The + * long-hand way to code this is as follows: + * + * <pre> + * {@code + * ToggleButton b1 = new ToggleButton("day"); + * ToggleButton b2 = new ToggleButton("week"); + * ToggleButton b3 = new ToggleButton("month"); + * ToggleButton b4 = new ToggleButton("year"); + * + * SegmentedButton segmentedButton = new SegmentedButton(); + * segmentedButton.getButtons().addAll(b1, b2, b3, b4);}</pre> + * + * <p>A slightly shorter way of doing this is to pass the ToggleButton instances + * into the varargs constructor, as such: + * + * <pre>{@code SegmentedButton segmentedButton = new SegmentedButton(b1, b2, b3, b4);}</pre> + * + * <h3>Custom ToggleGroup</h3> + * <p>It is possible to configure the ToggleGroup, which is used internally. + * By setting the ToggleGroup to null, the control will allow multiple selections. + * + * <pre> + * {@code + * SegmentedButton segmentedButton = new SegmentedButton(); + * segmentedButton.setToggleGroup(null); + * }</pre> + * + * <h3>Alternative Styling</h3> + * <p>As is visible in the screenshot at the top of this class documentation, + * there are two different styles supported by the SegmentedButton control. + * Firstly, there is the default style based on the JavaFX Modena look. The + * alternative style is what is currently referred to as the 'dark' look. To + * enable this functionality, simply do the following: + * + * <pre> + * {@code + * SegmentedButton segmentedButton = new SegmentedButton(); + * segmentedButton.getStyleClass().add(SegmentedButton.STYLE_CLASS_DARK); + * }</pre> + * + * <h3>Resizable Range</h3> + * <p>By default, the maximum width and height of a SegmentedButton match its + * preferred width and height. Thus, the SegmentedButton only fills the area + * which is necessary to display the contained buttons. To change this behavior, + * the following code can be used: + * + * <pre> + * {@code + * SegmentedButton segmentedButton = new SegmentedButton(); + * segmentedButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + * }</pre> + * + * @see ToggleButton + * @see ToggleGroup + */ +public class SegmentedButton extends ControlsFXControl { + + /************************************************************************** + * + * Static fields + * + *************************************************************************/ + + /** + * An alternative styling for the segmented button, with a darker pressed + * color which stands out more than the default modena styling. Refer to + * the class documentation for details on how to use (and screenshots), but + * in short, simply do the following to get the dark styling: + * + * <pre> + * {@code + * SegmentedButton segmentedButton = new SegmentedButton(); + * segmentedButton.getStyleClass().add(SegmentedButton.STYLE_CLASS_DARK); + * }</pre> + */ + public static final String STYLE_CLASS_DARK = "dark"; //$NON-NLS-1$ + + + + /************************************************************************** + * + * Private fields + * + *************************************************************************/ + + private final ObservableList<ToggleButton> buttons; + private final ObjectProperty<ToggleGroup> toggleGroup = new SimpleObjectProperty<>(new ToggleGroup()); + + /************************************************************************** + * + * Constructors + * + *************************************************************************/ + + /** + * Creates a default SegmentedButton instance with no buttons. + */ + public SegmentedButton() { + this((ObservableList<ToggleButton>)null); + } + + /** + * Creates a default SegmentedButton instance with the provided buttons + * inserted into it. + * + * @param buttons A varargs array of buttons to add into the SegmentedButton + * instance. + */ + public SegmentedButton(ToggleButton... buttons) { + this(buttons == null ? + FXCollections.<ToggleButton>observableArrayList() : + FXCollections.observableArrayList(buttons)); + } + + /** + * Creates a default SegmentedButton instance with the provided buttons + * inserted into it. + * + * @param buttons A list of buttons to add into the SegmentedButton instance. + */ + public SegmentedButton(ObservableList<ToggleButton> buttons) { + getStyleClass().add("segmented-button"); //$NON-NLS-1$ + this.buttons = buttons == null ? FXCollections.<ToggleButton>observableArrayList() : buttons; + + // Fix for Issue #87: + // https://bitbucket.org/controlsfx/controlsfx/issue/87/segmentedbutton-keyboard-focus-traversal + setFocusTraversable(false); + } + + + + + /************************************************************************** + * + * Public API + * + *************************************************************************/ + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new SegmentedButtonSkin(this); + } + + /** + * Returns the list of buttons that this SegmentedButton will draw together + * into one 'grouped button'. It is possible to modify this list to add or + * remove {@link ToggleButton} instances, as shown in the javadoc + * documentation for this class. + */ + public final ObservableList<ToggleButton> getButtons() { + return buttons; + } + + /** + * @return Property of the ToggleGroup used internally + */ + public ObjectProperty<ToggleGroup> toggleGroupProperty() { + return this.toggleGroup; + } + + /** + * @return ToggleGroup used internally + */ + public ToggleGroup getToggleGroup() { + return this.toggleGroupProperty().getValue(); + } + + /** + * @param toggleGroup ToggleGroup to be used internally + */ + public void setToggleGroup(final ToggleGroup toggleGroup) { + this.toggleGroupProperty().setValue(toggleGroup); + } + + + /************************************************************************** + * + * CSS + * + *************************************************************************/ + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(SegmentedButton.class, "segmentedbutton.css"); + } + +} \ No newline at end of file diff --git a/src/org/controlsfx/control/SnapshotView.java b/src/org/controlsfx/control/SnapshotView.java new file mode 100644 index 0000000000000000000000000000000000000000..6a9f48045bd08242c709551e7e509780e73870de --- /dev/null +++ b/src/org/controlsfx/control/SnapshotView.java @@ -0,0 +1,1733 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import static javafx.beans.binding.Bindings.and; +import static javafx.beans.binding.Bindings.isNotNull; +import static javafx.beans.binding.Bindings.notEqual; +import impl.org.controlsfx.skin.SnapshotViewSkin; +import impl.org.controlsfx.tools.rectangle.Rectangles2D; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableMap; +import javafx.css.CssMetaData; +import javafx.css.StyleConverter; +import javafx.css.Styleable; +import javafx.css.StyleableDoubleProperty; +import javafx.css.StyleableObjectProperty; +import javafx.css.StyleableProperty; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; +import javafx.scene.Node; +import javafx.scene.SnapshotParameters; +import javafx.scene.control.Control; +import javafx.scene.control.Skin; +import javafx.scene.image.WritableImage; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +/** + * A {@code SnapshotView} is a control which allows the user to select an area of a node in the typical manner used by + * picture editors and crate snapshots of the selection. + * <p> + * While holding the left mouse key down, a rectangular selection can be drawn. This selection can be moved, resized in + * eight cardinal directions and removed. Additionally, the selection's ratio can be fixed in which case the user's + * resizing will be limited such that the ratio is always upheld. + * <p> + * The area where the selection is possible is either this entire control or limited to the displayed node. + * + * <h3>Screenshots</h3> + * <center><img src="snapshotView.png" alt="Screenshot of SnapshotView"></center> + * + * <h3>Code Samples</h3> + * The following snippet creates a new instance with the ControlsFX logo loaded from the web, sets a selected area and + * fixes its ratio: + * + * <pre> + * ImageView controlsFxView = new ImageView( + * "http://cache.fxexperience.com/wp-content/uploads/2013/05/ControlsFX.png"); + * SnapshotView snapshotView = new SnapshotView(controlsFxView); + * snapshotView.setSelection(33, 50, 100, 100); + * snapshotView.setFixedSelectionRatio(1); // (this is actually the default value) + * snapshotView.setSelectionRatioFixed(true); + * </pre> + * + * <h3>Functionality Overview</h3> + * + * This is just a vague overview. The linked properties provide a more detailed explanation. + * + * <h4>Node</h4> + * + * The node which this control displays is held by the {@link #nodeProperty() node} property. + * + * <h4>Selection</h4> + * + * There are several properties which interact to manage and indicate the selection. + * + * <h5>State</h5> + * <ul> + * <li>the selection is held by the {@link #selectionProperty() selection} property + * <li>the {@link #hasSelectionProperty() hasSelection} property indicates whether a selection exists + * <li>the {@link #selectionActiveProperty() selectionActive} property indicates whether the current selection is active + * (it is only displayed if it is); by default this property is updated by this control which is determined by the + * {@link #selectionActivityManagedProperty() selectionActivityManaged} property + * </ul> + * + * <h5>Interaction</h5> + * <ul> + * <li>if the selection is changing due to the user interacting with the control, this is indicated by the + * {@link #selectionChangingProperty() selectionChanging} property + * <li>whether the user can select any area of the control or only one above the node is determined by the + * {@link #selectionAreaBoundaryProperty() selectionAreaBoundary} property + * <li>with the {@link #selectionMouseTransparentProperty() selectionMouseTransparent} property the control can be made + * mouse transparent so the user can interact with the displayed node + * <li>the selection's ratio of width to height can be fixed with the {@link #selectionRatioFixedProperty() + * selectionRatioFixed} and the {@link #fixedSelectionRatioProperty() fixedSelectionRatio} properties + * </ul> + * + * <h5>Visualization</h5> + * <ul> + * <li> {@link #selectionAreaFillProperty() selectionAreaFill} property for the selected area's paint + * <li> {@link #selectionBorderPaintProperty() selectionBorderPaint} property for the selection border's paint + * <li> {@link #selectionBorderWidthProperty() selectionBorderWidth} property for the selection border's width + * <li> {@link #unselectedAreaFillProperty() unselectedAreaFill} property for the area outside of the selection + * <li> {@link #unselectedAreaBoundaryProperty() unselectedAreaBoundary} property which defined what the unselected area + * covers + * </ul> + */ +public class SnapshotView extends ControlsFXControl { + + /** + * The maximal divergence between a selection's ratio and the {@link #fixedSelectionRatioProperty() + * fixedselectionRatio} for the selection to still have the correct ratio (see {@link #hasCorrectRatio(Rectangle2D) + * hasCorrectRatio}). + * <p> + * The divergence is expressed relative to the {@code fixedselectionRatio}. + */ + public static final double MAX_SELECTION_RATIO_DIVERGENCE = 1e-6; + + /** + * The key of the {@link #getProperties() property} which is used to update {@link #selectionChangingProperty() + * selectionChanging}. + */ + public static final String SELECTION_CHANGING_PROPERTY_KEY = + SnapshotView.class.getCanonicalName() + ".selection_changing"; //$NON-NLS-1$ + + /* ************************************************************************ + * * + * Attributes & Properties * + * * + **************************************************************************/ + + // NODE + + /** + * @see #nodeProperty() + */ + private final ObjectProperty<Node> node; + + // SELECTION + + /** + * @see #selectionProperty() + */ + private final ObjectProperty<Rectangle2D> selection; + + /** + * @see #hasSelectionProperty() + */ + private final BooleanProperty hasSelection; + + /** + * @see #selectionActiveProperty() + */ + private final BooleanProperty selectionActive; + + /** + * @see #selectionChangingProperty() + */ + private final BooleanProperty selectionChanging; + + /** + * @see #selectionRatioFixedProperty() + */ + private final BooleanProperty selectionRatioFixed; + + /** + * @see #fixedSelectionRatioProperty() + */ + private final DoubleProperty fixedSelectionRatio; + + // META + + /** + * @see #selectionAreaBoundaryProperty() + */ + private final ObjectProperty<Boundary> selectionAreaBoundary; + + /** + * @see #selectionActivityManagedProperty() + */ + private final BooleanProperty selectionActivityManaged; + + /** + * @see #selectionMouseTransparentProperty() + */ + private final BooleanProperty selectionMouseTransparent; + + // VISUALIZATION + + /** + * @see #unselectedAreaBoundaryProperty() + */ + private final ObjectProperty<Boundary> unselectedAreaBoundary; + + /** + * @see #selectionBorderPaintProperty() + */ + private final ObjectProperty<Paint> selectionBorderPaint; + + /** + * @see #selectionBorderWidthProperty() + */ + private final DoubleProperty selectionBorderWidth; + + /** + * @see #selectionAreaFillProperty() + */ + private final ObjectProperty<Paint> selectionAreaFill; + + /** + * @see #unselectedAreaFillProperty() + */ + private final ObjectProperty<Paint> unselectedAreaFill; + + /* ************************************************************************ + * * + * Construction * + * * + **************************************************************************/ + + /** + * Creates a new SnapshotView. + */ + public SnapshotView() { + getStyleClass().setAll(DEFAULT_STYLE_CLASS); + + // NODE + node = new SimpleObjectProperty<>(this, "node"); //$NON-NLS-1$ + + // SELECTION + selection = new SimpleObjectProperty<Rectangle2D>(this, "selection") { //$NON-NLS-1$ + @Override + public void set(Rectangle2D selection) { + if (!isSelectionValid(selection)) { + throw new IllegalArgumentException("The selection \"" + selection + "\" is invalid. " + //$NON-NLS-1$ //$NON-NLS-2$ + "Check the comment on 'SnapshotView.selectionProperty()' " + //$NON-NLS-1$ + "for all criteria a selection must fulfill."); //$NON-NLS-1$ + } + super.set(selection); + } + }; + hasSelection = new SimpleBooleanProperty(this, "hasSelection", false); //$NON-NLS-1$ + hasSelection.bind(and(isNotNull(selection), notEqual(Rectangle2D.EMPTY, selection))); + selectionActive = new SimpleBooleanProperty(this, "selectionActive", false); //$NON-NLS-1$ + selectionChanging = new SimpleBooleanProperty(this, "selectionChanging", false); //$NON-NLS-1$ + + selectionRatioFixed = new SimpleBooleanProperty(this, "selectionRatioFixed", false); //$NON-NLS-1$ + fixedSelectionRatio = new SimpleDoubleProperty(this, "fixedSelectionRatio", 1) { //$NON-NLS-1$ + @Override + public void set(double newValue) { + if (newValue <= 0) { + throw new IllegalArgumentException("The fixed selection ratio must be positive."); //$NON-NLS-1$ + } + super.set(newValue); + } + }; + + // META + selectionAreaBoundary = createStylableObjectProperty( + this, "selectionAreaBoundary", Boundary.CONTROL, Css.SELECTION_AREA_BOUNDARY); //$NON-NLS-1$ + selectionActivityManaged = new SimpleBooleanProperty(this, "selectionActivityManaged", true); //$NON-NLS-1$ + selectionMouseTransparent = new SimpleBooleanProperty(this, "selectionMouseTransparent", false); //$NON-NLS-1$ + + // VISUALIZATION + unselectedAreaBoundary = createStylableObjectProperty( + this, "unselectedAreaBoundary", Boundary.CONTROL, Css.UNSELECTED_AREA_BOUNDARY); //$NON-NLS-1$ + selectionBorderPaint = createStylableObjectProperty( + this, "selectionBorderPaint", Color.WHITESMOKE, Css.SELECTION_BORDER_PAINT); //$NON-NLS-1$ + selectionBorderWidth = createStylableDoubleProperty( + this, "selectionBorderWidth", 2.5, Css.SELECTION_BORDER_WIDTH); //$NON-NLS-1$ + selectionAreaFill = createStylableObjectProperty( + this, "selectionAreaFill", Color.TRANSPARENT, Css.SELECTION_AREA_FILL); //$NON-NLS-1$ + unselectedAreaFill = createStylableObjectProperty( + this, "unselectedAreaFill", new Color(0, 0, 0, 0.5), Css.UNSELECTED_AREA_FILL); //$NON-NLS-1$ + + addStateUpdatingListeners(); + // update selection when resizing + new SelectionSizeUpdater().enableResizing(); + } + + /** + * Adds listeners to the properties which update the control's state. + */ + private void addStateUpdatingListeners() { + // update the selection activity state when the selection is set + selection.addListener((o, oldValue, newValue) -> updateSelectionActivityState()); + + // ratio + selectionRatioFixed.addListener((o, oldValue, newValue) -> { + boolean valueChangedToTrue = !oldValue && newValue; + if (valueChangedToTrue) { + fixSelectionRatio(); + } + }); + fixedSelectionRatio.addListener((o, oldValue, newValue) -> { + if (isSelectionRatioFixed()) { + fixSelectionRatio(); + } + }); + + // set selection changing according to the values set in the property map + listenToProperty( + getProperties(), SELECTION_CHANGING_PROPERTY_KEY, (Boolean value) -> selectionChanging.set(value)); + } + + /** + * Listens to the specified properties. When a pair with the specified key is added, it is processed. If the value + * has the correct type, it is given to the specified consumer. Even if the type does not match, it is removed from + * the map. + * + * @param properties + * the {@link ObservableMap} which contains the properties; typically {@link Control#getProperties()} + * @param key + * the key for whose value is listened + * @param processValue + * the {@link Consumer} for the new value + */ + private static <T> void listenToProperty( + ObservableMap<Object, Object> properties, Object key, Consumer<T> processValue) { + + Objects.requireNonNull(properties, "The argument 'properties' must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(key, "The argument 'key' must not be null."); //$NON-NLS-1$ + Objects.requireNonNull(processValue, "The argument 'processValue' must not be null."); //$NON-NLS-1$ + + @SuppressWarnings("unchecked") + MapChangeListener<Object, Object> listener = change -> { + boolean addedForKey = + change.wasAdded() && Objects.equals(key, change.getKey()); + if (addedForKey) { + // give the value to the consumer if it has the correct type + try { + // note that this cast does nothing except to calm the compiler + // (hence the warning which had to be suppressed) + T newValue = (T) change.getValueAdded(); + // this is where the actual exception is created + processValue.accept(newValue); + } catch (ClassCastException e) { + // the value was of the wrong type so it can't be processed by the consumer + // -> do nothing + } + // remove the value from the properties map + properties.remove(key); + } + }; + + properties.addListener(listener); + } + + /** + * Creates a new SnapshotView using the specified node. + * + * @param node + * the node to show after construction + */ + public SnapshotView(Node node) { + this(); + setNode(node); + } + + /* ************************************************************************ + * * + * Public Methods * + * * + **************************************************************************/ + + /** + * Transforms the {@link #selectionProperty() selection} to node coordinates by calling + * {@link #transformToNodeCoordinates(Rectangle2D) transformToNodeCoordinates}. + * + * @return a {@link Rectangle2D} which expresses the selection in the node's coordinates + * @throws IllegalStateException + * if {@link #nodeProperty() node} is {@code null} or {@link #hasSelection() hasSelection} is + * {@code false} + * @see #transformToNodeCoordinates(Rectangle2D) + */ + public Rectangle2D transformSelectionToNodeCoordinates() { + if (!hasSelection()) { + throw new IllegalStateException( + "The selection can not be transformed if it does not exist (check 'hasSelection()')."); //$NON-NLS-1$ + } + + return transformToNodeCoordinates(getSelection()); + } + + /** + * Transforms the specified area's coordinates to coordinates relative to the node. (The node's coordinate system + * has its origin in the upper left corner of the node.) + * + * @param area + * the {@link Rectangle2D} which will be transformed (must not be {@code null}); its coordinates will be + * interpreted relative to the control (like the {@link #selectionProperty() selection}) + * @return a {@link Rectangle2D} with the same width and height as the specified {@code area} but with coordinates + * which are relative to the current {@link #nodeProperty() node} + * @throws IllegalStateException + * if {@link #nodeProperty() node} is {@code null} + */ + public Rectangle2D transformToNodeCoordinates(Rectangle2D area) throws IllegalStateException { + Objects.requireNonNull(area, "The argument 'area' must not be null."); //$NON-NLS-1$ + if (getNode() == null) { + throw new IllegalStateException( + "The selection can not be transformed if the node is null (check 'getNode()')."); //$NON-NLS-1$ + } + + // get the offset from the node's bounds + Bounds nodeBounds = getNode().getBoundsInParent(); + double xOffset = nodeBounds.getMinX(); + double yOffset = nodeBounds.getMinY(); + + // the coordinates of the transformed selection + double minX = area.getMinX() - xOffset; + double minY = area.getMinY() - yOffset; + + return new Rectangle2D(minX, minY, area.getWidth(), area.getHeight()); + } + + /** + * Creates a snapshot of the selected area of the node. + * + * @return the {@link WritableImage} that holds the rendered selection + * @throws IllegalStateException + * if {@link #nodeProperty() node} is {@code null} or {@link #hasSelection() hasSelection} is + * {@code false} + * @see Node#snapshot + */ + public WritableImage createSnapshot() throws IllegalStateException { + // make sure the node and the selection exist + if (getNode() == null) { + throw new IllegalStateException("No snapshot can be created if the node is null (check 'getNode()')."); //$NON-NLS-1$ + } + if (!hasSelection()) { + throw new IllegalStateException( + "No snapshot can be created if there is no selection (check 'hasSelection()')."); //$NON-NLS-1$ + } + + SnapshotParameters parameters = new SnapshotParameters(); + parameters.setViewport(getSelection()); + return createSnapshot(parameters); + } + + /** + * Creates a snapshot of the node with the specified parameters. + * + * @param parameters + * the {@link SnapshotParameters} used for the snapshot (must not be {@code null}); the viewport will be + * interpreted relative to this control (like the {@link #selectionProperty() selection}) + * @return the {@link WritableImage} that holds the rendered viewport + * @throws IllegalStateException + * if {@link #nodeProperty() node} is {@code null} + * @see Node#snapshot + */ + public WritableImage createSnapshot(SnapshotParameters parameters) throws IllegalStateException { + // make sure the node and the snapshot parameters exist + Objects.requireNonNull(parameters, "The argument 'parameters' must not be null."); //$NON-NLS-1$ + if (getNode() == null) { + throw new IllegalStateException("No snapshot can be created if the node is null (check 'getNode()')."); //$NON-NLS-1$ + } + + // take the snapshot + return getNode().snapshot(parameters, null); + } + + /* ************************************************************************ + * * + * Model State * + * * + **************************************************************************/ + + /** + * Updates the {@link #selectionActiveProperty() selectionActive} property if the + * {@link #selectionActivityManagedProperty() selectionActivityManaged} property indicates that it is managed by + * this control. + */ + private void updateSelectionActivityState() { + boolean userManaged = !isSelectionActivityManaged(); + if (userManaged) { + return; + } + + boolean selectionActive = getSelection() != null && getSelection() != Rectangle2D.EMPTY; + setSelectionActive(selectionActive); + } + + /** + * Resizes the current selection (if it exists) to the {@link #fixedSelectionRatioProperty() fixedSelectionRatio}. + */ + private void fixSelectionRatio() { + boolean noSelectionToFix = getNode() == null || !hasSelection(); + if (noSelectionToFix) { + return; + } + + Rectangle2D selectionBounds = getSelectionBounds(); + Rectangle2D resizedSelection = Rectangles2D.fixRatioWithinBounds( + getSelection(), getFixedSelectionRatio(), selectionBounds); + + selection.set(resizedSelection); + } + + /** + * + * @return the bounds of the current selection according to the {@link #selectionAreaBoundaryProperty() + * selectionAreaBoundary}. + */ + private Rectangle2D getSelectionBounds() { + Boundary boundary = getSelectionAreaBoundary(); + switch (boundary) { + case CONTROL: + return new Rectangle2D(0, 0, getWidth(), getHeight()); + case NODE: + return Rectangles2D.fromBounds(getNode().getBoundsInParent()); + default: + throw new IllegalArgumentException("The boundary '" + boundary + "' is not fully implemented yet."); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Checks whether the specified selection is valid. This includes checking whether the selection is in bounds and + * has the correct ratio (if the ratio is fixed). + * + * @param selection + * the selection to check as a {@link Rectangle2D} + * @return {@code true} if the selection is valid; {@code false} otherwise + */ + private boolean isSelectionValid(Rectangle2D selection) { + // empty selections are valid + boolean emptySelection = selection == null || selection == Rectangle2D.EMPTY; + if (emptySelection) { + return true; + } + + // check values + if (!valuesFinite(selection)) { + return false; + } + + // check bounds + if (!inBounds(selection)) { + return false; + } + + // check ratio + if (!hasCorrectRatio(selection)) { + return false; + } + + return true; + } + + /** + * Indicates whether the specified selection has only finite values (e.g. width and height). + * + * @param selection + * the selection as a {@link Rectangle2D} + * @return {@code true} if the selection has only finite values. + */ + private static boolean valuesFinite(Rectangle2D selection) { + return Double.isFinite(selection.getMinX()) && Double.isFinite(selection.getMinY()) && + Double.isFinite(selection.getWidth()) && Double.isFinite(selection.getHeight()); + } + + /** + * Indicates whether the specified selection is inside the bounds determined by the + * {@link #selectionAreaBoundaryProperty() selectionAreaBoundary} property. + * + * @param selection + * the non-null and non-empty selection as a {@link Rectangle2D} + * @return {@code true} if the selection is fully contained in the bounds; otherwise {@code false} + */ + private boolean inBounds(Rectangle2D selection) { + Boundary boundary = getSelectionAreaBoundary(); + switch (boundary) { + case CONTROL: + return inBounds(selection, getBoundsInLocal()); + case NODE: + if (getNode() == null) { + return false; + } else { + return inBounds(selection, getNode().getBoundsInParent()); + } + default: + throw new IllegalArgumentException("The boundary '" + boundary + "' is not fully implemented yet."); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Indicates whether the specified selection is inside the specified bounds. + * + * @param selection + * the selection as a {@link Rectangle2D} + * @param bounds + * the {@link Bounds} to check the selection against + * @return {@code true} if the selection is fully contained in the bounds; otherwise {@code false} + */ + private static boolean inBounds(Rectangle2D selection, Bounds bounds) { + return bounds.getMinX() <= selection.getMinX() && bounds.getMinY() <= selection.getMinY() && + selection.getMaxX() <= bounds.getMaxX() && selection.getMaxY() <= bounds.getMaxY(); + } + + /** + * Indicates whether the specified selection has the correct ratio (which depends on whether the ratio is even + * {@link #selectionRatioFixedProperty() fixed}). + * + * @param selection + * the selection to check as a {@link Rectangle2D} + * @return {@code true} if the selection has the correct ratio. + */ + private boolean hasCorrectRatio(Rectangle2D selection) { + if (!isSelectionRatioFixed()) { + return true; + } + + double ratio = selection.getWidth() / selection.getHeight(); + // compute the divergence relative to the fixed selection ratio + double ratioDivergence = Math.abs(1 - ratio / getFixedSelectionRatio()); + return ratioDivergence <= MAX_SELECTION_RATIO_DIVERGENCE; + } + + /* ************************************************************************ + * * + * Style Sheet & Skin Handling * + * * + **************************************************************************/ + + /** + * The name of the style class used in CSS for instances of this class. + */ + private static final String DEFAULT_STYLE_CLASS = "snapshot-view"; //$NON-NLS-1$ + + /** {@inheritDoc} */ + @Override + public String getUserAgentStylesheet() { + return getUserAgentStylesheet(SnapshotView.class, "snapshot-view.css"); //$NON-NLS-1$ + } + + /** + * Creates a {@link StyleableDoubleProperty} with the specified arguments. + * + * @param bean + * the {@link Property#getBean() bean} the created property belongs to + * @param name + * the property's {@link Property#getName() name} + * @param initialValue + * the property's initial value + * @param cssMetaData + * the {@link CssMetaData} for the created property + * @return a {@link StyleableDoubleProperty} + */ + private static StyleableDoubleProperty createStylableDoubleProperty( + Object bean, String name, double initialValue, CssMetaData<? extends Styleable, Number> cssMetaData) { + + return new StyleableDoubleProperty(initialValue) { + + @Override + public Object getBean() { + return bean; + } + + @Override + public String getName() { + return name; + } + + @Override + public CssMetaData<? extends Styleable, Number> getCssMetaData() { + return cssMetaData; + } + + }; + } + + /** + * Creates a {@link StyleableObjectProperty} with the specified arguments. + * + * @param bean + * the {@link Property#getBean() bean} the created property belongs to + * @param name + * the property's {@link Property#getName() name} + * @param initialValue + * the property's initial value + * @param cssMetaData + * the {@link CssMetaData} for the created property + * @return a {@link StyleableObjectProperty} + */ + private static <T> StyleableObjectProperty<T> createStylableObjectProperty( + Object bean, String name, T initialValue, CssMetaData<? extends Styleable, T> cssMetaData) { + + return new StyleableObjectProperty<T>(initialValue) { + + @Override + public Object getBean() { + return bean; + } + + @Override + public String getName() { + return name; + } + + @Override + public CssMetaData<? extends Styleable, T> getCssMetaData() { + return cssMetaData; + } + + }; + } + + /** + * Creates an instance of {@link CssMetaData} with the specified arguments. + * + * @param getProperty + * a function from the {@link Styleable} which owns the styled property to the property styled by the + * returned {@code CssMetaData} + * @param cssPropertyName + * the name by which the styled property is referenced in CSS files + * @param styleConverter + * the {@link StyleConverter} used to convert the CSS parsed value to a Java object + * @return an instance of {@link CssMetaData} + */ + private static <S extends Styleable, T> CssMetaData<S, T> createCssMetaData( + Function<S, Property<T>> getProperty, String cssPropertyName, StyleConverter<?, T> styleConverter) { + + return new CssMetaData<S, T>(cssPropertyName, styleConverter) { + + @Override + public boolean isSettable(S styleable) { + final Property<T> property = getProperty.apply(styleable); + return property != null && !property.isBound(); + } + + @Override + @SuppressWarnings("unchecked") + public StyleableProperty<T> getStyleableProperty(S styleable) { + return (StyleableProperty<T>) getProperty.apply(styleable); + } + }; + } + + /** + * The class which holds this control's {@link CssMetaData} for the different {@link StyleableProperty + * StyleableProperties}. + */ + @SuppressWarnings({ "javadoc", "unchecked" }) + private static class Css { + + public static final CssMetaData<SnapshotView, Boundary> SELECTION_AREA_BOUNDARY = + createCssMetaData( + snapshotView -> snapshotView.selectionAreaBoundary, "-fx-selection-area-boundary", //$NON-NLS-1$ + (StyleConverter<?, Boundary>) StyleConverter.getEnumConverter(Boundary.class)); + + public static final CssMetaData<SnapshotView, Boundary> UNSELECTED_AREA_BOUNDARY = + createCssMetaData( + snapshotView -> snapshotView.unselectedAreaBoundary, "-fx-unselected-area-boundary", //$NON-NLS-1$ + (StyleConverter<?, Boundary>) StyleConverter.getEnumConverter(Boundary.class)); + + public static final CssMetaData<SnapshotView, Paint> SELECTION_BORDER_PAINT = + createCssMetaData( + snapshotView -> snapshotView.selectionBorderPaint, "-fx-selection-border-paint", //$NON-NLS-1$ + StyleConverter.getPaintConverter()); + + public static final CssMetaData<SnapshotView, Number> SELECTION_BORDER_WIDTH = + createCssMetaData( + snapshotView -> snapshotView.selectionBorderWidth, "-fx-selection-border-width", //$NON-NLS-1$ + StyleConverter.getSizeConverter()); + + public static final CssMetaData<SnapshotView, Paint> SELECTION_AREA_FILL = + createCssMetaData( + snapshotView -> snapshotView.selectionAreaFill, "-fx-selection-area-fill", //$NON-NLS-1$ + StyleConverter.getPaintConverter()); + + public static final CssMetaData<SnapshotView, Paint> UNSELECTED_AREA_FILL = + createCssMetaData( + snapshotView -> snapshotView.unselectedAreaFill, "-fx-unselected-area-fill", //$NON-NLS-1$ + StyleConverter.getPaintConverter()); + + /** + * The {@link CssMetaData} associated with this class, which includes the {@code CssMetaData} of its super + * classes. + */ + public static final List<CssMetaData<? extends Styleable, ?>> CSS_META_DATA; + + static { + final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Control.getClassCssMetaData()); + styleables.add(SELECTION_AREA_BOUNDARY); + styleables.add(UNSELECTED_AREA_BOUNDARY); + styleables.add(SELECTION_BORDER_PAINT); + styleables.add(SELECTION_BORDER_WIDTH); + styleables.add(SELECTION_AREA_FILL); + styleables.add(UNSELECTED_AREA_FILL); + CSS_META_DATA = Collections.unmodifiableList(styleables); + } + } + + /** + * @return the {@link CssMetaData} associated with this class, which includes the {@code CssMetaData} of its super + * classes + */ + public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { + return Css.CSS_META_DATA; + } + + @Override + public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { + return getClassCssMetaData(); + } + + @Override + protected Skin<?> createDefaultSkin() { + return new SnapshotViewSkin(this); + } + + /* ************************************************************************ + * * + * Property Access * + * * + **************************************************************************/ + + // NODE + + /** + * The {@link Node} which will be displayed in the center of this control. + * <p> + * The node's {@link Node#boundsInParentProperty() boundsInParent} show its relative position inside this control. + * Since the {@link #selectionProperty() selection} property also uses this control as its reference coordinate + * system, the bounds can be used to compute which area of the node is selected. + * <p> + * If this control or the node behaves strangely when resized, try embedding the original node in a {@link Pane} and + * setting the pane here. + * + * @return the property holding the displayed node + */ + public final ObjectProperty<Node> nodeProperty() { + return node; + } + + /** + * @return the displayed node + * @see #nodeProperty() + */ + public final Node getNode() { + return nodeProperty().get(); + } + + /** + * @param node + * the node to display + * @see #nodeProperty() + */ + public final void setNode(Node node) { + nodeProperty().set(node); + } + + // SELECTION + + /** + * The current selection as a {@link Rectangle2D}. As such an instance is immutable a new one must be set to chane + * the selection. + * <p> + * The rectangle's coordinates are interpreted relative to this control. The top left corner is the origin (0, 0) + * and the lower right corner is ({@link #widthProperty() width}, {@link #heightProperty() height}). It is + * guaranteed that the selection always lies within these bounds. If the control is resized, so is the selection. If + * a selection which violates these bounds is set, an {@link IllegalArgumentException} is thrown. + * <p> + * The same is true if the {@link #selectionAreaBoundaryProperty() selectionAreaBoundary} is set to {@code NODE} but + * with the stricter condition that the selection must lie within the {@link #nodeProperty() node}'s + * {@link Node#boundsInParentProperty() boundsInParent}. + * <p> + * If the selection ratio is {@link #selectionRatioFixedProperty() fixed}, any new selection must have the + * {@link #fixedSelectionRatioProperty() fixedSelectionRatio}. Otherwise, an {@code IllegalArgumentException} is + * thrown. + * <p> + * An {@code IllegalArgumentException} is also thrown if not all of the selection's values (e.g. width and height) + * are finite. + * <p> + * The selection might be {@code null} or {@link Rectangle2D#EMPTY} in which case no selection is displayed and + * {@link #hasSelectionProperty() hasSelection} is {@code false}. + * + * @return the property holding the current selection + * @see #hasSelectionProperty() + */ + public final ObjectProperty<Rectangle2D> selectionProperty() { + return selection; + } + + /** + * @return the current selection + * @see #selectionProperty() + */ + public final Rectangle2D getSelection() { + return selectionProperty().get(); + } + + /** + * @param selection + * the new selection + * @throws IllegalArgumentException + * if the selection is out of the bounds defined by the {@link #selectionAreaBoundaryProperty() + * selectionAreaBoundary} or the selection ratio is {@link #selectionRatioFixedProperty() fixed} and the + * new selection does not have the {@link #fixedSelectionRatioProperty() fixedSelectionRatio}. + * @see #selectionProperty() + */ + public final void setSelection(Rectangle2D selection) { + selectionProperty().set(selection); + } + + /** + * Creates a new {@link Rectangle2D} from the specified arguments and sets it as the new + * {@link #selectionProperty() selection}. It will have ({@code upperLeftX}, {@code upperLeftY}) as its upper left + * point and span {@code width} to the right and {@code height} down. + * + * @param upperLeftX + * the x coordinate of the selection's upper left point + * @param upperLeftY + * the y coordinate of the selection's upper left point + * @param width + * the selection's width + * @param height + * the selection's height + * @throws IllegalArgumentException + * if the selection is out of the bounds defined by the {@link #selectionAreaBoundaryProperty() + * selectionAreaBoundary} or the selection ratio is {@link #selectionRatioFixedProperty() fixed} and the + * new selection does not have the {@link #fixedSelectionRatioProperty() fixedSelectionRatio}. + * @see #selectionProperty() + * + */ + public final void setSelection(double upperLeftX, double upperLeftY, double width, double height) { + selectionProperty().set(new Rectangle2D(upperLeftX, upperLeftY, width, height)); + } + + /** + * Indicates whether there currently is a selection. This will be {@code false} if the {@link #selectionProperty() + * selection} property holds {@code null} or {@link Rectangle2D#EMPTY} . + * + * @return a property indicating whether there currently is a selection + */ + public final ReadOnlyBooleanProperty hasSelectionProperty() { + return hasSelection; + } + + /** + * @return whether there currently is a selection + * @see #hasSelectionProperty() + */ + public final boolean hasSelection() { + return hasSelectionProperty().get(); + } + + /** + * Indicates whether the selection is currently active. Only an active selection will be displayed by the control. + * <p> + * See {@link #selectionActivityManagedProperty() selectionActivityManaged} for documentation on how this property + * might be changed by this control. + * + * @return the property indicating whether the selection is active + */ + public final BooleanProperty selectionActiveProperty() { + return selectionActive; + } + + /** + * @return whether the selection is active + * @see #selectionActiveProperty() + */ + public final boolean isSelectionActive() { + return selectionActiveProperty().get(); + } + + /** + * @param selectionActive + * the new selection active status + * @see #selectionActiveProperty() + */ + public final void setSelectionActive(boolean selectionActive) { + selectionActiveProperty().set(selectionActive); + } + + /** + * Indicates whether the {@link #selectionProperty() selection} is currently changing due to user interaction with + * the control. It will be set to {@code true} when changing the selection begins and set to {@code false} when it + * ends. + * <p> + * If a selection is set by the code using this control (e.g. by calling {@link #setSelection(Rectangle2D) + * setSelection}) this property does not change its value. + * + * @return a property indicating whether the selection is changing by user interaction + */ + public final ReadOnlyBooleanProperty selectionChangingProperty() { + return selectionChanging; + } + + /** + * @return whether the selection is changing by user interaction + * @see #selectionChangingProperty() + */ + public final boolean isSelectionChanging() { + return selectionChangingProperty().get(); + } + + /** + * Indicates whether the ratio of the {@link #selectionProperty() selection} is fixed. + * <p> + * By default this property is {@code false} and the user interacting with this control can make arbitrary + * selections with any ratio of width to height. If it is {@code true}, the user is limited to making selections + * with the ratio defined by the {@link #fixedSelectionRatioProperty() fixedSelectionRatio} property. If the ratio + * is fixed and a selection with a different ratio is set, an {@link IllegalArgumentException} is thrown. + * <p> + * If a selection exists and this property is set to {@code true}, the selection is immediately resized to the + * currently set ratio. + * + * @defaultValue {@code false} + * @return the property indicating whether the selection ratio is fixed + */ + public final BooleanProperty selectionRatioFixedProperty() { + return selectionRatioFixed; + } + + /** + * @return whether the selection ratio is fixed + * @see #selectionRatioFixedProperty() + */ + public final boolean isSelectionRatioFixed() { + return selectionRatioFixedProperty().get(); + } + + /** + * @param selectionRatioFixed + * whether the selection ratio will be fixed + * @see #selectionRatioFixedProperty() + */ + public final void setSelectionRatioFixed(boolean selectionRatioFixed) { + selectionRatioFixedProperty().set(selectionRatioFixed); + } + + /** + * The value to which the selection ratio is fixed. The ratio is defined as {@code width / height} and its value + * must be strictly positive. + * <p> + * If {@link #selectionRatioFixedProperty() selectionRatioFixed} is {@code true}, this ratio will be upheld by all + * changes made by user interaction with this control. If the ratio is fixed and a selection is set by code (e.g. by + * calling {@link #setSelection(Rectangle2D) setSelection}), this ratio is checked and if violated an + * {@link IllegalArgumentException} is thrown. + * <p> + * If a selection exists and {@code selectionRatioFixed} is set to {@code true}, the selection is immediately + * resized to this ratio. Similarly, if a selection exists and its ratio is fixed, setting a new value resizes the + * selection to the new ratio. + * + * @defaultValue 1.0 + * @return a property containing the fixed selection ratio + */ + public final DoubleProperty fixedSelectionRatioProperty() { + return fixedSelectionRatio; + } + + /** + * @return the fixedSelectionRatio, which will always be a strictly positive value + * @see #fixedSelectionRatioProperty() + */ + public final double getFixedSelectionRatio() { + return fixedSelectionRatioProperty().get(); + } + + /** + * @param fixedSelectionRatio + * the fixed selection ratio to set + * @throws IllegalArgumentException + * if {@code fixedSelectionRatio} is not strictly positive + * @see #fixedSelectionRatioProperty() + */ + public final void setFixedSelectionRatio(double fixedSelectionRatio) { + fixedSelectionRatioProperty().set(fixedSelectionRatio); + } + + // META + + /** + * Indicates which {@link Boundary} is set for the area the user can select. + * <p> + * By default the user can select any area of the control. If this should be limited to the area over the displayed + * node instead, this property can be set to {@link Boundary#NODE NODE}. If the value is changed from + * {@code CONTROL} to {@code NODE} a possibly existing selection is resized accordingly. + * <p> + * If the boundary is set to {@code NODE}, this is also respected when a new {@link #selectionProperty() selection} + * is set. This means the condition for the new selection's coordinates is made stricter and setting a selection out + * of the node's bounds (instead of only out of the control's bounds) throws an {@link IllegalArgumentException}. + * <p> + * Note that this does <b>not</b> change the reference coordinate system! The selection's coordinates are still + * interpreted relative to the {@link #nodeProperty() node}'s {@link Node#boundsInParentProperty() boundsInParent}. + * + * @defaultValue {@link Boundary#CONTROL CONTROL} + * @return the property indicating the {@link Boundary} for the area the user can select + */ + public final ObjectProperty<Boundary> selectionAreaBoundaryProperty() { + return selectionAreaBoundary; + } + + /** + * @return the {@link Boundary} for the area the user can select + */ + public final Boundary getSelectionAreaBoundary() { + return selectionAreaBoundaryProperty().get(); + } + + /** + * @param selectionAreaBoundary + * the new {@link Boundary} for the area the user can select + */ + public final void setSelectionAreaBoundary(Boundary selectionAreaBoundary) { + selectionAreaBoundaryProperty().set(selectionAreaBoundary); + } + + /** + * Indicates whether the value of the {@link #selectionActiveProperty() selectionActive} property is managed by this + * control. + * <p> + * If this property is set to {@code true} (which is the default) this control will update the + * {@code selectionActive} property immediately after a new selection is set: if the new selection is {@code null} + * or {@link Rectangle2D#EMPTY}, it will be set to {@code false}; otherwise to {@code true}. + * <p> + * If this property is {@code false} this control will never change {@code selectionActive}'s value. In this case it + * must be managed by the using code but it is possible to unidirectionally bind it to another property without this + * control interfering. + * + * @defaultValue {@code true} + * @return the property indicating whether the value of the {@link #selectionActiveProperty() selectionActive} + * property is managed by this control + */ + public final BooleanProperty selectionActivityManagedProperty() { + return selectionActivityManaged; + } + + /** + * @return whether the selection activity is managed by this control + * @see #selectionActivityManagedProperty() + */ + public final boolean isSelectionActivityManaged() { + return selectionActivityManagedProperty().get(); + } + + /** + * @param selectionActivityManaged + * whether the selection activity will be managed by this control + * @see #selectionActivityManagedProperty() + */ + public final void setSelectionActivityManaged(boolean selectionActivityManaged) { + selectionActivityManagedProperty().set(selectionActivityManaged); + } + + /** + * Indicates whether the overlay which displays the selection is mouse transparent. + * <p> + * By default all mouse events are captured by this control and used to interact with the selection. If this + * property is set to {@code true}, this behavior changes and the user is able to interact with the displayed + * {@link #nodeProperty() node}. + * + * @defaultValue {@code false} + * @return the property indicating whether the selection is mouse transparent + */ + public final BooleanProperty selectionMouseTransparentProperty() { + return selectionMouseTransparent; + } + + /** + * @return whether the selection is mouse transparent + * @see #selectionMouseTransparentProperty() + */ + public final boolean isSelectionMouseTransparent() { + return selectionMouseTransparentProperty().get(); + } + + /** + * @param selectionMouseTransparent + * whether the selection will be mouse transparent + * @see #selectionMouseTransparentProperty() + */ + public final void setSelectionMouseTransparent(boolean selectionMouseTransparent) { + selectionMouseTransparentProperty().set(selectionMouseTransparent); + } + + // VISUALIZATION + + /** + * Indicates which {@link Boundary} is set for the visualization of the unselected area (i.e. the area outside of + * the selection rectangle). + * <p> + * If it is set to {@link Boundary#CONTROL CONTROL} (which is the default), the unselected area covers the whole + * control. + * <p> + * If it is set to {@link Boundary#NODE NODE}, the area only covers the displayed {@link #nodeProperty() node}. In + * most cases this only makes sense if the {@link #selectionAreaBoundaryProperty() selectionAreaBoundary} is also + * set to {@code NODE}. + * + * @defaultValue {@link Boundary#CONTROL} + * @return the property defining the {@link Boundary} of the unselected area + */ + public final ObjectProperty<Boundary> unselectedAreaBoundaryProperty() { + return unselectedAreaBoundary; + } + + /** + * @return the {@link Boundary} for the unselected area + * @see #unselectedAreaBoundaryProperty() + */ + public final Boundary getUnselectedAreaBoundary() { + return unselectedAreaBoundaryProperty().get(); + } + + /** + * @param unselectedAreaBoundary + * the new {@link Boundary} for the unselected area + * @see #unselectedAreaBoundaryProperty() + */ + public final void setUnselectedAreaBoundary(Boundary unselectedAreaBoundary) { + unselectedAreaBoundaryProperty().set(unselectedAreaBoundary); + } + + /** + * Determines the visualization of the selection's border. + * + * @defaultValue {@link Color#WHITESMOKE} + * @return the property holding the {@link Paint} of the selection border + * @see #selectionBorderWidthProperty() + */ + public final ObjectProperty<Paint> selectionBorderPaintProperty() { + return selectionBorderPaint; + } + + /** + * @return the {@link Paint} of the selection border + * @see #selectionBorderPaintProperty() + */ + public final Paint getSelectionBorderPaint() { + return selectionBorderPaintProperty().get(); + } + + /** + * @param selectionBorderPaint + * the new {@link Paint} of the selection border + * @see #selectionBorderPaintProperty() + */ + public final void setSelectionBorderPaint(Paint selectionBorderPaint) { + selectionBorderPaintProperty().set(selectionBorderPaint); + } + + /** + * Determines the width of the selection's border. The border is always painted to the outside of the selected area, + * i.e. the selected area is never covered by the border. + * + * @defaultValue 2.5 + * @return the property defining the selection border's width + * @see #selectionBorderPaintProperty() + * @see javafx.scene.shape.Shape#strokeWidthProperty() Shape.strokeWidthProperty() + */ + public final DoubleProperty selectionBorderWidthProperty() { + return selectionBorderWidth; + } + + /** + * @return the selection border width + * @see #selectionBorderWidthProperty() + */ + public final double getSelectionBorderWidth() { + return selectionBorderWidthProperty().get(); + } + + /** + * @param selectionBorderWidth + * the selection border width to set + * @see #selectionBorderWidthProperty() + */ + public final void setSelectionBorderWidth(double selectionBorderWidth) { + selectionBorderWidthProperty().set(selectionBorderWidth); + } + + /** + * Determines the visualization of the selected area. + * + * @defaultValue {@link Color#TRANSPARENT} + * @return the property holding the {@link Paint} of the selected area + */ + public final ObjectProperty<Paint> selectionAreaFillProperty() { + return selectionAreaFill; + } + + /** + * @return the {@link Paint} of the selected area + * @see #selectionAreaFillProperty() + */ + public final Paint getSelectionAreaFill() { + return selectionAreaFillProperty().get(); + } + + /** + * @param selectionAreaFill + * the new {@link Paint} of the selected area + * @see #selectionAreaFillProperty() + */ + public final void setSelectionAreaFill(Paint selectionAreaFill) { + selectionAreaFillProperty().set(selectionAreaFill); + } + + /** + * Determines the visualization of the area outside of the selection. + * + * @defaultValue {@link Color#BLACK black} with {@link Color#getOpacity() opacity} 0.5 + * @return the property holding the {@link Paint} of the area outside of the selection + */ + public final ObjectProperty<Paint> unselectedAreaFillProperty() { + return unselectedAreaFill; + } + + /** + * @return the {@link Paint} of the area outside of the selection + * @see #unselectedAreaFillProperty() + */ + public final Paint getUnselectedAreaFill() { + return unselectedAreaFillProperty().get(); + } + + /** + * @param unselectedAreaFill + * the new {@link Paint} of the area outside of the selection + * @see #unselectedAreaFillProperty() + */ + public final void setUnselectedAreaFill(Paint unselectedAreaFill) { + unselectedAreaFillProperty().set(unselectedAreaFill); + } + + /* ************************************************************************ + * * + * Inner Classes * + * * + **************************************************************************/ + + /** + * The {@link SnapshotView#selectionAreaBoundaryProperty() selectionArea}, in which the user can create a selection, + * and the {@link SnapshotView#unselectedAreaBoundaryProperty() unselectedArea}, in which the unselected area is + * visualized, are limited to a certain area of the control. This area's boundary is represented by this enum. + * + */ + public static enum Boundary { + + /** + * The boundary is this control's bound. + */ + CONTROL, + + /** + * The boundary is the displayed node's bound. + */ + NODE, + + } + + /** + * Updates the size of the {@link SnapshotView#selectionProperty() selection} whenever necessary. This is the case + * if the {@link SnapshotView#selectionAreaBoundaryProperty() selectionAreaBoundary} is set to + * {@link Boundary#CONTROL CONTROL} and the control is resized or when it is set to {@link Boundary#NODE NODE} and + * the node is changed or resized. + * + */ + private class SelectionSizeUpdater { + + /* + * If the 'selectionAreaBoundary' is set to 'CONTROL', the selection is only updated when the control changes + * its width or height. If it is set to 'NODE', the selection is resized whenever the node or its + * 'boundsInParent' change. + * For both cases methods exist which resize the selection. The listeners which call those methods are only + * added to the corresponding properties when the matching boundary is selected. + */ + + // CONTROL + + /** + * Calls {@link #resizeSelectionToNewControlWidth(ObservableValue, Number, Number) + * updateSelectionToNewControlWidth} whenever the control's width changes. + */ + private final ChangeListener<Number> resizeSelectionToNewControlWidthListener; + + /** + * Calls {@link #resizeSelectionToNewControlHeight(ObservableValue, Number, Number) + * updateSelectionToNewControlWidth} whenever the control's height changes. + */ + private final ChangeListener<Number> resizeSelectionToNewControlHeightListener; + + // NODE + + /** + * Calls {@link #updateSelectionToNewNode(ObservableValue, Node, Node) updateSelectionToNewNode} whenever a new + * {@link SnapshotView#nodeProperty() node} is set. + */ + private final ChangeListener<Node> updateSelectionToNodeListener; + + /** + * Calls {@link #resizeSelectionToNewNodeBounds(ObservableValue, Bounds, Bounds) updateSelectionToNewNodeBounds} + * whenever the node's {@link Node#boundsInParentProperty() boundsInParent} change. + */ + private final ChangeListener<Bounds> resizeSelectionToNewNodeBoundsListener; + + // CONSTRUCTION + + /** + * Creates a new selection size updater. + */ + public SelectionSizeUpdater() { + // create listeners which point to methods + resizeSelectionToNewControlWidthListener = this::resizeSelectionToNewControlWidth; + resizeSelectionToNewControlHeightListener = this::resizeSelectionToNewControlHeight; + updateSelectionToNodeListener = this::updateSelectionToNewNode; + resizeSelectionToNewNodeBoundsListener = this::resizeSelectionToNewNodeBounds; + } + + // ENABLE RESIZING + + /** + * Enables resizing of the control. + */ + public void enableResizing() { + // only resize if the selection is not null + enableResizingForBoundary(getSelectionAreaBoundary()); + selectionAreaBoundary.addListener((o, oldBoundary, newBoundary) -> enableResizingForBoundary(newBoundary)); + } + + /** + * Enables resizing for the specified boundary. + * + * @param boundary + * the {@link Boundary} for which the control will be resized. + */ + private void enableResizingForBoundary(Boundary boundary) { + switch (boundary) { + case CONTROL: + enableResizingForControl(); + break; + case NODE: + enableResizingForNode(); + break; + default: + throw new IllegalArgumentException("The boundary '" + boundary + "' is not fully implemented yet."); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Enables resizing if the {@link SnapshotView#selectionAreaBoundary selectionAreaBoundary} is + * {@link Boundary#CONTROL CONTROL}. + */ + private void enableResizingForControl() { + // remove listeners for node and its bounds + node.removeListener(updateSelectionToNodeListener); + if (getNode() != null) { + getNode().boundsInParentProperty().removeListener(resizeSelectionToNewNodeBoundsListener); + } + + // add listener for the control's size + widthProperty().addListener(resizeSelectionToNewControlWidthListener); + heightProperty().addListener(resizeSelectionToNewControlHeightListener); + + resizeSelectionFromNodeToControl(); + } + + /** + * Enables resizing if the {@link SnapshotView#selectionAreaBoundary selectionAreaBoundary} is + * {@link Boundary#NODE NODE}. + */ + private void enableResizingForNode() { + // remove listeners for the control's size + widthProperty().removeListener(resizeSelectionToNewControlWidthListener); + heightProperty().removeListener(resizeSelectionToNewControlHeightListener); + + // add listener for the node's bounds and for new nodes + if (getNode() != null) { + getNode().boundsInParentProperty().addListener(resizeSelectionToNewNodeBoundsListener); + } + node.addListener(updateSelectionToNodeListener); + + resizeSelectionFromControlToNode(); + } + + // RESIZE TO CONTROL + + /** + * Resizes the current {@link SnapshotView#selectionProperty() selection} from the node's to the control's + * bounds. + */ + private void resizeSelectionFromNodeToControl() { + if (getNode() == null) { + setSelection(null); + } else { + // transform the selection from the control's to the node's bounds + Rectangle2D controlBounds = new Rectangle2D(0, 0, getWidth(), getHeight()); + Rectangle2D nodeBounds = Rectangles2D.fromBounds(getNode().getBoundsInParent()); + resizeSelectionToNewBounds(nodeBounds, controlBounds); + } + } + + /** + * Resizes the current {@link SnapshotView#selectionProperty() selection} from the control's specified old width + * to its specified new width. + * <p> + * Designed to be used as a lambda method reference. + * + * @param o + * the {@link ObservableValue} which changed its value + * @param oldWidth + * the control's old width + * @param newWidth + * the control's new width + */ + private void resizeSelectionToNewControlWidth( + @SuppressWarnings("unused") ObservableValue<? extends Number> o, Number oldWidth, Number newWidth) { + + Rectangle2D oldBounds = new Rectangle2D(0, 0, oldWidth.doubleValue(), getHeight()); + Rectangle2D newBounds = new Rectangle2D(0, 0, newWidth.doubleValue(), getHeight()); + resizeSelectionToNewBounds(oldBounds, newBounds); + } + + /** + * Resizes the current {@link SnapshotView#selectionProperty() selection} from the control's specified old + * height to its specified new height. + * <p> + * Designed to be used as a lambda method reference. + * + * @param o + * the {@link ObservableValue} which changed its value + * @param oldHeight + * the control's old height + * @param newHeight + * the control's new height + */ + private void resizeSelectionToNewControlHeight( + @SuppressWarnings("unused") ObservableValue<? extends Number> o, Number oldHeight, Number newHeight) { + + Rectangle2D oldBounds = new Rectangle2D(0, 0, getWidth(), oldHeight.doubleValue()); + Rectangle2D newBounds = new Rectangle2D(0, 0, getWidth(), newHeight.doubleValue()); + resizeSelectionToNewBounds(oldBounds, newBounds); + } + + // RESIZE TO NODE + + /** + * Resizes the current {@link SnapshotView#selectionProperty() selection} from the control's to the node's + * bounds + */ + private void resizeSelectionFromControlToNode() { + if (getNode() == null) { + setSelection(null); + } else { + // transform the selection from the control's to the node's bounds + Rectangle2D controlBounds = new Rectangle2D(0, 0, getWidth(), getHeight()); + Rectangle2D nodeBounds = Rectangles2D.fromBounds(getNode().getBoundsInParent()); + resizeSelectionToNewBounds(controlBounds, nodeBounds); + } + } + + /** + * Moves the {@link #resizeSelectionToNewNodeBoundsListener} from the specified old to the specified new node's + * {@link Node#boundsInParentProperty() boundsInParent} property and resizes the current + * {@link SnapshotView#selectionProperty() selection} from the old to the new node's bounds. + * <p> + * Designed to be used as a lambda method reference. + * + * @param o + * the {@link ObservableValue} which changed its value + * @param oldNode + * the old node + * @param newNode + * the new node + */ + private void updateSelectionToNewNode( + @SuppressWarnings("unused") ObservableValue<? extends Node> o, Node oldNode, Node newNode) { + + // move the bounds listener from the old to the new node + if (oldNode != null) { + oldNode.boundsInParentProperty().removeListener(resizeSelectionToNewNodeBoundsListener); + } + if (newNode != null) { + newNode.boundsInParentProperty().addListener(resizeSelectionToNewNodeBoundsListener); + } + + // update selection + if (oldNode == null || newNode == null) { + // if one of the nodes is null, set no selection + setSelection(null); + } else { + // transform the current selection + resizeSelectionToNewNodeBounds(null, oldNode.getBoundsInParent(), newNode.getBoundsInParent()); + } + } + + /** + * Resizes the current {@link SnapshotView#selectionProperty() selection} from the specified old to the + * specified new bounds of the {@link SnapshotView#nodeProperty() node}. + * + * @param o + * the {@link ObservableValue} which changed its value + * @param oldBounds + * the node's old bounds + * @param newBounds + * the node's new bounds + */ + private void resizeSelectionToNewNodeBounds( + @SuppressWarnings("unused") ObservableValue<? extends Bounds> o, Bounds oldBounds, Bounds newBounds) { + + resizeSelectionToNewBounds(Rectangles2D.fromBounds(oldBounds), Rectangles2D.fromBounds(newBounds)); + } + + // GENERAL RESIZING + + /** + * If this control {@link SnapshotView#hasSelection() has a selection} it is resized from the specified old to + * the specified new bounds. + * + * @param oldBounds + * the {@link SnapshotView#selectionProperty() selection}'s old bounds as a {@link Rectangle2D} + * @param newBounds + * the {@link SnapshotView#selectionProperty() selection}'s new bounds as a {@link Rectangle2D} + */ + private void resizeSelectionToNewBounds(Rectangle2D oldBounds, Rectangle2D newBounds) { + if (!hasSelection()) { + return; + } + + Rectangle2D newSelection = transformSelectionToNewBounds(getSelection(), oldBounds, newBounds); + if (isSelectionValid(newSelection)) { + setSelection(newSelection); + } else { + setSelection(null); + } + } + + /** + * Returns a new selection which is a transformation of the specified old selection. The transformation is such + * that the new selection's "relative position" in the specified new bounds is the same as the old selection's + * relative position in the specified old bounds. + * <p> + * Here, "relative position" is a representation of the selection where the coordinates of its upper left point + * and its width and height are expressed in a percentage of its bounds. Those percentages are the same for + * "old selection in old bounds" and "returned selection in new bounds" + * + * @param oldSelection + * the selection to be transformed as a {@link Rectangle2D} + * @param oldBounds + * the {@code oldSelection}'s old bounds as a {@link Rectangle2D} + * @param newBounds + * the {@code oldSelection}'s new bounds as a {@link Rectangle2D} + * @return s {@link Rectangle2D} which is the transformation of the old selection to the new bounds + */ + private Rectangle2D transformSelectionToNewBounds( + Rectangle2D oldSelection, Rectangle2D oldBounds, Rectangle2D newBounds) { + + Point2D newSelectionCenter = computeNewSelectionCenter(oldSelection, oldBounds, newBounds); + + double widthRatio = newBounds.getWidth() / oldBounds.getWidth(); + double heightRatio = newBounds.getHeight() / oldBounds.getHeight(); + + if (isSelectionRatioFixed()) { + double newArea = (oldSelection.getWidth() * widthRatio) * (oldSelection.getHeight() * heightRatio); + double ratio = getFixedSelectionRatio(); + return Rectangles2D.forCenterAndAreaAndRatioWithinBounds(newSelectionCenter, newArea, ratio, newBounds); + } else { + double newWidth = oldSelection.getWidth() * widthRatio; + double newHeight = oldSelection.getHeight() * heightRatio; + return Rectangles2D.forCenterAndSize(newSelectionCenter, newWidth, newHeight); + } + } + + /** + * Computes a point with the same relative position in the specified new bounds as the specified old selection's + * center point in the specified old bounds. (See + * {@link #transformSelectionToNewBounds(Rectangle2D, Rectangle2D, Rectangle2D) transformSelectionToNewBounds} + * for a definition of "relative position"). + * + * @param oldSelection + * the selection whose center point is the base for the returned center point as a + * {@link Rectangle2D} + * @param oldBounds + * the bounds of the old selection as a {@link Rectangle2D} + * @param newBounds + * the bounds for the new selection as a {@link Rectangle2D} + * @return a {@link Point2D} with the same relative position in the new bounds as the old selection's center + * point in the old bounds + */ + private Point2D computeNewSelectionCenter(Rectangle2D oldSelection, Rectangle2D oldBounds, Rectangle2D newBounds) { + + Point2D oldSelectionCenter = Rectangles2D.getCenterPoint(oldSelection); + Point2D oldBoundsCenter = Rectangles2D.getCenterPoint(oldBounds); + Point2D oldSelectionCenterOffset = oldSelectionCenter.subtract(oldBoundsCenter); + + double widthRatio = newBounds.getWidth() / oldBounds.getWidth(); + double heightRatio = newBounds.getHeight() / oldBounds.getHeight(); + + Point2D newSelectionCenterOffset = new Point2D( + oldSelectionCenterOffset.getX() * widthRatio, oldSelectionCenterOffset.getY() * heightRatio); + Point2D newBoundsCenter = Rectangles2D.getCenterPoint(newBounds); + Point2D newSelectionCenter = newBoundsCenter.add(newSelectionCenterOffset); + + return newSelectionCenter; + } + + } + +} diff --git a/src/org/controlsfx/control/StatusBar.java b/src/org/controlsfx/control/StatusBar.java new file mode 100644 index 0000000000000000000000000000000000000000..40273de30471aad5476915bc0edf683aa208e7cb --- /dev/null +++ b/src/org/controlsfx/control/StatusBar.java @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; + +import impl.org.controlsfx.skin.StatusBarSkin; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.Skin; + +/** + * The StatusBar control is normally placed at the bottom of a window. It is + * used to display various types of application status information. This can be + * a text message, the progress of a task, or any other kind of status (e.g. red + * / green / yellow lights). By default the status bar contains a label for + * displaying plain text and a progress bar (see {@link ProgressBar}) for long + * running tasks. Additional controls / nodes can be placed on the left and + * right sides (see {@link #getLeftItems()} and {@link #getRightItems()}). + * + * <h3>Screenshots</h3> + * The picture below shows the default appearance of the StatusBar control: + * <center><img src="statusbar.png" alt="Screenshot of StatusBar"></center> + * + * <br> + * The following picture shows the status bar reporting progress of a task: + * <center><img src="statusbar-progress.png" alt="Screenshot of StatusBar + * reporting progress of a task"></center> + * + * <br> + * The last picture shows the status bar reporting progress, along with a couple + * of extra items added to the left and right areas of the bar: + * <center><img src="statusbar-items.png" alt="Screenshot of StatusBar + * reporting progress, along with a couple of extra items"></center> + * + * <h3>Code Sample</h3> + * + * <pre> + * StatusBar statusBar = new StatusBar(); + * statusBar.getLeftItems().add(new Button("Info")); + * statusBar.setProgress(.5); + * </pre> + */ +public class StatusBar extends ControlsFXControl { + + /** + * Constructs a new status bar control. + */ + public StatusBar() { + getStyleClass().add("status-bar"); //$NON-NLS-1$ + } + + @Override + protected Skin<?> createDefaultSkin() { + return new StatusBarSkin(this); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(StatusBar.class, "statusbar.css"); + } + + private final StringProperty text = new SimpleStringProperty(this, "text", //$NON-NLS-1$ + localize(asKey("statusbar.ok"))); //$NON-NLS-1$ + + /** + * The property used for storing the text message shown by the status bar. + * + * @return the text message property + */ + public final StringProperty textProperty() { + return text; + } + + /** + * Sets the value of the {@link #textProperty()}. + * + * @param text the text shown by the label control inside the status bar + */ + public final void setText(String text) { + textProperty().set(text); + } + + /** + * Returns the value of the {@link #textProperty()}. + * + * @return the text currently shown by the status bar + */ + public final String getText() { + return textProperty().get(); + } + + private final ObjectProperty<Node> graphic = new SimpleObjectProperty<>( + this, "graphic"); //$NON-NLS-1$ + + /** + * The property used to store a graphic node that can be displayed by the + * status label inside the status bar control. + * + * @return the property used for storing a graphic node + */ + public final ObjectProperty<Node> graphicProperty() { + return graphic; + } + + /** + * Returns the value of the {@link #graphicProperty()}. + * + * @return the graphic node shown by the label inside the status bar + */ + public final Node getGraphic() { + return graphicProperty().get(); + } + + /** + * Sets the value of {@link #graphicProperty()}. + * + * @param node the graphic node shown by the label inside the status bar + */ + public final void setGraphic(Node node) { + graphicProperty().set(node); + } + + private final ObservableList<Node> leftItems = FXCollections + .observableArrayList(); + + /** + * Returns the list of items / nodes that will be shown to the left of the status label. + * + * @return the items on the left-hand side of the status bar + */ + public final ObservableList<Node> getLeftItems() { + return leftItems; + } + + private final ObservableList<Node> rightItems = FXCollections + .observableArrayList(); + + /** + * Returns the list of items / nodes that will be shown to the right of the status label. + * + * @return the items on the left-hand side of the status bar + */ + public final ObservableList<Node> getRightItems() { + return rightItems; + } + + private final DoubleProperty progress = new SimpleDoubleProperty(this, + "progress"); //$NON-NLS-1$ + + /** + * The property used to store the progress, a value between 0 and 1. A negative + * value causes the progress bar to show an indeterminate state. + * + * @return the property used to store the progress of a task + */ + public final DoubleProperty progressProperty() { + return progress; + } + + /** + * Sets the value of the {@link #progressProperty()}. + * + * @param progress the new progress value + */ + public final void setProgress(double progress) { + progressProperty().set(progress); + } + + /** + * Returns the value of {@link #progressProperty()}. + * + * @return the current progress value + */ + public final double getProgress() { + return progressProperty().get(); + } +} diff --git a/src/org/controlsfx/control/TaskProgressView.java b/src/org/controlsfx/control/TaskProgressView.java new file mode 100644 index 0000000000000000000000000000000000000000..839d4ceb360e5b2f5d0789e9d2dd3bff4ad34e52 --- /dev/null +++ b/src/org/controlsfx/control/TaskProgressView.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control; + +import impl.org.controlsfx.skin.TaskProgressViewSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.concurrent.WorkerStateEvent; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.util.Callback; + +/** + * The task progress view is used to visualize the progress of long running + * tasks. These tasks are created via the {@link Task} class. This view + * manages a list of such tasks and displays each one of them with their + * name, progress, and update messages.<p> + * An optional graphic factory can be set to place a graphic in each row. + * This allows the user to more easily distinguish between different types + * of tasks. + * + * <h3>Screenshots</h3> + * The picture below shows the default appearance of the task progress view + * control: + * <center><img src="task-monitor.png" alt="Screenshot of TaskProgressView"></center> + * + * <h3>Code Sample</h3> + * <pre> + * TaskProgressView<MyTask> view = new TaskProgressView<>(); + * view.setGraphicFactory(task -> return new ImageView("db-access.png")); + * view.getTasks().add(new MyTask()); + * </pre> + */ +public class TaskProgressView<T extends Task<?>> extends ControlsFXControl { + + /** + * Constructs a new task progress view. + */ + public TaskProgressView() { + getStyleClass().add("task-progress-view"); + + EventHandler<WorkerStateEvent> taskHandler = evt -> { + if (evt.getEventType().equals( + WorkerStateEvent.WORKER_STATE_SUCCEEDED) + || evt.getEventType().equals( + WorkerStateEvent.WORKER_STATE_CANCELLED) + || evt.getEventType().equals( + WorkerStateEvent.WORKER_STATE_FAILED)) { + getTasks().remove(evt.getSource()); + } + }; + + getTasks().addListener(new ListChangeListener<Task<?>>() { + @Override + public void onChanged(Change<? extends Task<?>> c) { + while (c.next()) { + if (c.wasAdded()) { + for (Task<?> task : c.getAddedSubList()) { + task.addEventHandler(WorkerStateEvent.ANY, + taskHandler); + } + } else if (c.wasRemoved()) { + for (Task<?> task : c.getRemoved()) { + task.removeEventHandler(WorkerStateEvent.ANY, + taskHandler); + } + } + } + } + }); + } + + /** {@inheritDoc} */ + @Override public String getUserAgentStylesheet() { + return getUserAgentStylesheet(TaskProgressView.class, "taskprogressview.css"); + } + + @Override + protected Skin<?> createDefaultSkin() { + return new TaskProgressViewSkin<>(this); + } + + private final ObservableList<T> tasks = FXCollections + .observableArrayList(); + + /** + * Returns the list of tasks currently monitored by this view. + * + * @return the monitored tasks + */ + public final ObservableList<T> getTasks() { + return tasks; + } + + private ObjectProperty<Callback<T, Node>> graphicFactory; + + /** + * Returns the property used to store an optional callback for creating + * custom graphics for each task. + * + * @return the graphic factory property + */ + public final ObjectProperty<Callback<T, Node>> graphicFactoryProperty() { + if (graphicFactory == null) { + graphicFactory = new SimpleObjectProperty<Callback<T, Node>>( + this, "graphicFactory"); + } + + return graphicFactory; + } + + /** + * Returns the value of {@link #graphicFactoryProperty()}. + * + * @return the optional graphic factory + */ + public final Callback<T, Node> getGraphicFactory() { + return graphicFactory == null ? null : graphicFactory.get(); + } + + /** + * Sets the value of {@link #graphicFactoryProperty()}. + * + * @param factory an optional graphic factory + */ + public final void setGraphicFactory(Callback<T, Node> factory) { + graphicFactoryProperty().set(factory); + } +} diff --git a/src/org/controlsfx/control/ToggleSwitch.java b/src/org/controlsfx/control/ToggleSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..d380eaddbfbb04f8799586a44850c64de0c2af9c --- /dev/null +++ b/src/org/controlsfx/control/ToggleSwitch.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.controlsfx.control; + +import impl.org.controlsfx.skin.ToggleSwitchSkin; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.BooleanPropertyBase; +import javafx.css.PseudoClass; +import javafx.event.ActionEvent; +import javafx.scene.control.Labeled; +import javafx.scene.control.Skin; + +/** + * Much like a Toggle Button this control allows the user to toggle between one of two states. It has been popularized + * in touch based devices where its usage is particularly useful because unlike a checkbox the finger touch of a user + * doesn't obscure the control. + * + * <p> Shown below is a screenshot of the ToggleSwitch control in its on and off state: + * <br> + * <center> + * <img src="ToggleSwitch.png" alt="Screenshot of ToggleSwitch"> + * </center> + */ +public class ToggleSwitch extends Labeled +{ + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Creates a toggle switch with empty string for its label. + */ + public ToggleSwitch() { + initialize(); + } + + /** + * Creates a toggle switch with the specified label. + * + * @param text The label string of the control. + */ + public ToggleSwitch(String text) { + super(text); + initialize(); + } + + private void initialize() { + getStyleClass().setAll(DEFAULT_STYLE_CLASS); + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * Indicates whether this ToggleSwitch is selected. + */ + private BooleanProperty selected; + + /** + * Sets the selected value of this Toggle Switch + */ + public final void setSelected(boolean value) { + selectedProperty().set(value); + } + + /** + * Returns whether this Toggle Switch is selected + */ + public final boolean isSelected() { + return selected == null ? false : selected.get(); + } + + /** + * Returns the selected property + */ + public final BooleanProperty selectedProperty() { + if (selected == null) { + selected = new BooleanPropertyBase() { + @Override protected void invalidated() { + final Boolean v = get(); + pseudoClassStateChanged(PSEUDO_CLASS_SELECTED, v); +// accSendNotification(Attribute.SELECTED); + } + + @Override + public Object getBean() { + return ToggleSwitch.this; + } + + @Override + public String getName() { + return "selected"; + } + }; + } + return selected; + } + + + /*************************************************************************** + * * + * Methods * + * * + **************************************************************************/ + + /** + * Toggles the state of the {@code ToggleSwitch}. The {@code ToggleSwitch} will cycle through + * the selected and unselected states. + */ + public void fire() { + if (!isDisabled()) { + setSelected(!isSelected()); + fireEvent(new ActionEvent()); + } + } + + /** {@inheritDoc} */ + @Override protected Skin<?> createDefaultSkin() { + return new ToggleSwitchSkin(this); + } + + + /*************************************************************************** + * * + * Stylesheet Handling * + * * + **************************************************************************/ + + private static final String DEFAULT_STYLE_CLASS = "toggle-switch"; + + private static final PseudoClass PSEUDO_CLASS_SELECTED = + PseudoClass.getPseudoClass("selected"); + + /** {@inheritDoc} */ + @Override + public String getUserAgentStylesheet() { + return ToggleSwitch.class.getResource("toggleswitch.css").toExternalForm(); + } + +} diff --git a/src/org/controlsfx/control/action/Action.java b/src/org/controlsfx/control/action/Action.java new file mode 100644 index 0000000000000000000000000000000000000000..04d84396097f0056afa9b270e0d557ad69f7612e --- /dev/null +++ b/src/org/controlsfx/control/action/Action.java @@ -0,0 +1,430 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import impl.org.controlsfx.i18n.Localization; +import impl.org.controlsfx.i18n.SimpleLocalizedStringProperty; + +import java.util.function.Consumer; + +import javafx.beans.NamedArg; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.input.KeyCombination; + +/** + * A base class for Action API. + * + * <h3>What is an Action?</h3> + * An action in JavaFX can be used to separate functionality and state from a + * control. For example, if you have two or more controls that perform the same + * function (e.g. one in a {@link Menu} and another on a toolbar), consider + * using an Action object to implement the function. An Action object provides + * centralized handling of the state of action-event-firing components such as + * buttons, menu items, etc. The state that an action can handle includes text, + * graphic, long text (i.e. tooltip text), and disabled. + */ +public class Action implements EventHandler<ActionEvent> { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private boolean locked = false; + + private Consumer<ActionEvent> eventHandler; + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + public Action(@NamedArg("text") String text) { + this(text, null); + } + + public Action(Consumer<ActionEvent> eventHandler) { + this("", eventHandler); //$NON-NLS-1$ + } + + /** + * Creates a new AbstractAction instance with the given String set as the + * {@link #textProperty() text} value, as well as the {@code Consumer<ActionEvent>} + * set to be called when the action event is fired. + * + * @param text The string to display in the text property of controls such + * as {@link Button#textProperty() Button}. + * @param eventHandler This will be called when the ActionEvent is fired. + */ + public Action(@NamedArg("text") String text, Consumer<ActionEvent> eventHandler) { + setText(text); + setEventHandler(eventHandler); + getStyleClass().add( "action" ); // this class will be added to all bound controls + } + + protected void lock() { + locked = true; + } + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- style + /** + * A string representation of the CSS style associated with this + * Action instance and passed to related UI controls. + * This is analogous to the "style" attribute of an + * HTML element. Note that, like the HTML style attribute, this + * variable contains style properties and values and not the + * selector portion of a style rule. + * <p> + * Parsing this style might not be supported on some limited + * platforms. It is recommended to use a standalone CSS file instead. + */ + private StringProperty style; + public final void setStyle(String value) { styleProperty().set(value); } + public final String getStyle() { return style == null ? "" : style.get(); } //$NON-NLS-1$ + public final StringProperty styleProperty() { + if (style == null) { + style = new SimpleStringProperty(this, "style") { //$NON-NLS-1$ + @Override + public void set(String style) { + if (locked) throw new UnsupportedOperationException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(style); + } + }; + } + return style; + } + + + // --- Style class + private final ObservableList<String> styleClass = FXCollections.observableArrayList(); + /** + * A list of String identifiers which can be used to logically group + * Nodes, specifically for an external style engine. This variable is + * analogous to the "class" attribute on an HTML element and, as such, + * each element of the list is a style class to which this Node belongs. + * + * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a> + */ + public ObservableList<String> getStyleClass() { + return styleClass; + } + + + // --- selected + private final BooleanProperty selectedProperty = new SimpleBooleanProperty(this, "selected") { //$NON-NLS-1$ + @Override public void set(boolean selected) { + if (locked) throw new UnsupportedOperationException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(selected); + } + }; + + /** + * Represents action's selected state. + * Usually bound to selected state of components such as Toggle Buttons, CheckBOxes etc + * + * @return An observable {@link BooleanProperty} that represents the current + * selected state, and which can be observed for changes. + */ + public final BooleanProperty selectedProperty() { + return selectedProperty; + } + + /** + * Selected state of the Action. + * @return The selected state of this action. + */ + public final boolean isSelected() { + return selectedProperty.get(); + } + + /** + * Sets selected state of the Action + * @param selected + */ + public final void setSelected( boolean selected ) { + selectedProperty.set(selected); + } + + + // --- text + private final StringProperty textProperty = new SimpleLocalizedStringProperty(this, "text"){ //$NON-NLS-1$ + @Override public void set(String value) { + if ( locked ) throw new RuntimeException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(value); + } + }; + + /** + * The text to show to the user. + * + * @return An observable {@link StringProperty} that represents the current + * text for this property, and which can be observed for changes. + */ + public final StringProperty textProperty() { + return textProperty; + } + + /** + * + * @return the text of the Action. + */ + public final String getText() { + return textProperty.get(); + } + + /** + * Sets the text of the Action. + * @param value + */ + public final void setText(String value) { + textProperty.set(value); + } + + + // --- disabled + private final BooleanProperty disabledProperty = new SimpleBooleanProperty(this, "disabled"){ //$NON-NLS-1$ + @Override public void set(boolean value) { + if ( locked ) throw new RuntimeException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(value); + } + }; + + /** + * This represents whether the action should be available to the end user, + * or whether it should appeared 'grayed out'. + * + * @return An observable {@link BooleanProperty} that represents the current + * disabled state for this property, and which can be observed for + * changes. + */ + public final BooleanProperty disabledProperty() { + return disabledProperty; + } + + /** + * + * @return whether the action is available to the end user, + * or whether it should appeared 'grayed out'. + */ + public final boolean isDisabled() { + return disabledProperty.get(); + } + + /** + * Sets whether the action should be available to the end user, + * or whether it should appeared 'grayed out'. + * @param value + */ + public final void setDisabled(boolean value) { + disabledProperty.set(value); + } + + + // --- longText + private final StringProperty longTextProperty = new SimpleLocalizedStringProperty(this, "longText"){ //$NON-NLS-1$ + @Override public void set(String value) { + if ( locked ) throw new RuntimeException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(value); + + } + }; + + /** + * The longer form of the text to show to the user (e.g. on a + * {@link Button}, it is usually a tooltip that should be shown to the user + * if their mouse hovers over this action). + * + * @return An observable {@link StringProperty} that represents the current + * long text for this property, and which can be observed for changes. + */ + public final StringProperty longTextProperty() { + return longTextProperty; + } + + /** + * @see #longTextProperty() + * @return The longer form of the text to show to the user + */ + public final String getLongText() { + return Localization.localize(longTextProperty.get()); + } + + /** + * Sets the longer form of the text to show to the user + * @param value + * @see #longTextProperty() + */ + public final void setLongText(String value) { + longTextProperty.set(value); + } + + + // --- graphic + private final ObjectProperty<Node> graphicProperty = new SimpleObjectProperty<Node>(this, "graphic"){ //$NON-NLS-1$ + @Override public void set(Node value) { + if ( locked ) throw new RuntimeException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(value); + + } + }; + + /** + * The graphic that should be shown to the user in relation to this action. + * + * @return An observable {@link ObjectProperty} that represents the current + * graphic for this property, and which can be observed for changes. + */ + public final ObjectProperty<Node> graphicProperty() { + return graphicProperty; + } + + /** + * + * @return The graphic that should be shown to the user in relation to this action. + */ + public final Node getGraphic() { + return graphicProperty.get(); + } + + /** + * Sets the graphic that should be shown to the user in relation to this action. + * @param value + */ + public final void setGraphic(Node value) { + graphicProperty.set(value); + } + + + // --- accelerator + private final ObjectProperty<KeyCombination> acceleratorProperty = new SimpleObjectProperty<KeyCombination>(this, "accelerator"){ //$NON-NLS-1$ + @Override public void set(KeyCombination value) { + if ( locked ) throw new RuntimeException("The action is immutable, property change support is disabled."); //$NON-NLS-1$ + super.set(value); + + } + }; + + /** + * The accelerator {@link KeyCombination} that should be used for this action, + * if it is used in an applicable UI control (most notably {@link MenuItem}). + * + * @return An observable {@link ObjectProperty} that represents the current + * accelerator for this property, and which can be observed for changes. + */ + public final ObjectProperty<KeyCombination> acceleratorProperty() { + return acceleratorProperty; + } + + /** + * + * @return The accelerator {@link KeyCombination} that should be used for this action, + * if it is used in an applicable UI control + */ + public final KeyCombination getAccelerator() { + return acceleratorProperty.get(); + } + + /** + * Sets the accelerator {@link KeyCombination} that should be used for this action, + * if it is used in an applicable UI control + * @param value + */ + public final void setAccelerator(KeyCombination value) { + acceleratorProperty.set(value); + } + + + // --- properties + private ObservableMap<Object, Object> props; + + /** + * Returns an observable map of properties on this Action for use primarily + * by application developers. + * + * @return An observable map of properties on this Action for use primarily + * by application developers + */ + public final synchronized ObservableMap<Object, Object> getProperties() { + if ( props == null ) props = FXCollections.observableHashMap(); + return props; + } + + protected Consumer<ActionEvent> getEventHandler() { + return eventHandler; + } + + protected void setEventHandler(Consumer<ActionEvent> eventHandler) { + this.eventHandler = eventHandler; + } + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * Defers to the {@code Consumer<ActionEvent>} passed in to the Action constructor. + */ + @Override public final void handle(ActionEvent event) { + if (eventHandler != null && !isDisabled()) { + eventHandler.accept(event); + } + } + +// public void bind(ButtonBase button) { +// ActionUtils.configureButton(this, button); +// } +// +// public void bind(MenuItem menuItem) { +// ActionUtils.configureMenuItem(this, menuItem); +// } +} \ No newline at end of file diff --git a/src/org/controlsfx/control/action/ActionCheck.java b/src/org/controlsfx/control/action/ActionCheck.java new file mode 100644 index 0000000000000000000000000000000000000000..b59ed061125546331353db1601daf281fab4927a --- /dev/null +++ b/src/org/controlsfx/control/action/ActionCheck.java @@ -0,0 +1,21 @@ +package org.controlsfx.control.action; + +import javafx.scene.control.Button; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.MenuItem; +import javafx.scene.control.ToggleButton; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks the {@link Action} or a method annotated with {@link ActionProxy} to let action engine know + * that {@link ToggleButton} or {@link CheckMenuItem} has to be bound to the action + * instead of standard {@link Button} and {@link MenuItem} + */ +@Target( { ElementType.TYPE, ElementType.METHOD } ) +@Retention(RetentionPolicy.RUNTIME) +public @interface ActionCheck { +} diff --git a/src/org/controlsfx/control/action/ActionGroup.java b/src/org/controlsfx/control/action/ActionGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..06ab1f00c33aae68bb500fc45e7eb12ee24f2f8d --- /dev/null +++ b/src/org/controlsfx/control/action/ActionGroup.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import java.util.Arrays; +import java.util.Collection; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuBar; +import javafx.scene.control.ToolBar; + +/** + * An ActionGroup (unsurprisingly) groups together zero or more {@link Action} + * instances, allowing for more complex controls like {@link ToolBar}, + * {@link MenuBar} and {@link ContextMenu} to be automatically generated from + * the collection of actions inside the ActionGroup. For your convenience, + * there are a number of utility methods that do precisely this in the + * {@link ActionUtils} class. + * + * <h3>Code Examples</h3> + * <p>Consider the following code example (note that DummyAction is a fake class + * that extends from (and implements) {@link Action}): + * + * <pre> + * {@code + * // Firstly, create a list of Actions + * Collection<? extends Action> actions = Arrays.asList( + * new ActionGroup("Group 1", new DummyAction("Action 1.1"), + * new DummyAction("Action 2.1") ), + * new ActionGroup("Group 2", new DummyAction("Action 2.1"), + * new ActionGroup("Action 2.2", new DummyAction("Action 2.2.1"), + * new DummyAction("Action 2.2.2")), + * new DummyAction("Action 2.3") ), + * new ActionGroup("Group 3", new DummyAction("Action 3.1"), + * new DummyAction("Action 3.2") ) + * ); + * + * // Use the ActionUtils class to create UI controls from these actions, e.g: + * MenuBar menuBar = ActionUtils.createMenuBar(actions); + * + * ToolBar toolBar = ActionUtils.createToolBar(actions); + * + * Label context = new Label("Right-click to see the context menu"); + * context.setContextMenu(ActionUtils.createContextMenu(actions)); + * }</pre> + * + * <p>The end result of running the code above is shown in the screenshots below + * (hopefully it goes without saying that within the 'Group 1', 'Group 2' and + * 'Group 3' options are the 'Action 1.1', etc actions that have been specified + * in the code above): + * + * <table border="0" summary="ActionGroup Screenshots"> + * <tr> + * <td width="75" valign="center"><strong>MenuBar:</strong></td> + * <td><img src="actionGroup-menubar.png" alt="Screenshot of ActionGroup in a MenuBar"></td> + * </tr> + * <tr> + * <td width="75" valign="center"><strong>ToolBar:</strong></td> + * <td><img src="actionGroup-toolbar.png" alt="Screenshot of ActionGroup in a ToolBar"></td> + * </tr> + * <tr> + * <td width="75" valign="top"><strong>ContextMenu:</strong></td> + * <td><img src="actionGroup-contextmenu.png" alt="Screenshot of ActionGroup in a ContextMenu"></td> + * </tr> + * </table> + * + * @see Action + * @see ActionUtils + */ +public class ActionGroup extends Action { + + /** + * Creates an ActionGroup with the given text as the name of the {@link Action}, + * and zero or more Actions as members of this ActionGroup. Note that it is + * legitimate to pass in zero Actions to this constructor, and to later + * set the actions directly into the {@link #getActions() actions} list. + * + * @param text The {@link Action#textProperty() text} of this {@link Action}. + * @param actions Zero or more actions to insert into this ActionGroup. + */ + public ActionGroup(String text, Action... actions) { + this(text, Arrays.asList(actions)); + } + + /** + * Creates an ActionGroup with the given text as the name of the {@link Action}, + * and collection of Actions as members of this ActionGroup. + * + * @param text The {@link Action#textProperty() text} of this {@link Action}. + * @param actions Collection of actions to insert into this ActionGroup. + */ + public ActionGroup(String text, Collection<Action> actions) { + super(text); + getActions().addAll(actions); + } + + /** + * Creates an ActionGroup with the given text as the name of the {@link Action}, + * and zero or more Actions as members of this ActionGroup. Note that it is + * legitimate to pass in zero Actions to this constructor, and to later + * set the actions directly into the {@link #getActions() actions} list. + * + * @param text The {@link Action#textProperty() text} of this {@link Action}. + * @param icon The {@link Action#graphicProperty() image} of this {@link Action}. + * @param actions Zero or more actions to insert into this ActionGroup. + */ + public ActionGroup(String text, Node icon, Action... actions) { + this( text, icon, Arrays.asList(actions)); + } + + /** + * Creates an ActionGroup with the given text as the name of the {@link Action}, + * and collection of Actions as members of this ActionGroup. . + * + * @param text The {@link Action#textProperty() text} of this {@link Action}. + * @param icon The {@link Action#graphicProperty() image} of this {@link Action}. + * @param actions Collection of actions to insert into this ActionGroup. + */ + public ActionGroup(String text, Node icon, Collection<Action> actions) { + super(text); + setGraphic(icon); + getActions().addAll(actions); + } + + // --- actions + private final ObservableList<Action> actions = FXCollections.<Action> observableArrayList(); + + /** + * The list of {@link Action} instances that exist within this ActionGroup. + * This list may be modified, as shown in the class documentation. + */ + public final ObservableList<Action> getActions() { + return actions; + } + + @Override public String toString() { + return getText(); + } + +} diff --git a/src/org/controlsfx/control/action/ActionMap.java b/src/org/controlsfx/control/action/ActionMap.java new file mode 100644 index 0000000000000000000000000000000000000000..a8e838009cf6d0cda6efe414411e49d45bc373a9 --- /dev/null +++ b/src/org/controlsfx/control/action/ActionMap.java @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import javafx.event.ActionEvent; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.*; + +/** + * Action Map provides an ability to create an action map of any object. + * Attempts to convert methods annotated with {@link ActionProxy} to {@link Action}. + * + * <h3>Code Example</h3> + * Here's a very simple example of how to use ActionMap to register a class (in + * this class it is the application class itself), and to then retrieve actions + * out of the ActionMap (via the static {@link ActionMap#action(String)} method: + * <br> + * + * <pre> + * public class ActionMapDemo extends Application { + * public ActionMapDemo() { + * ActionMap.register(this); + * Action action11 = ActionMap.action("action11"); + * Button actionButton = ActionUtils.createButton(action11); + * } + * + * @ActionProxy(text="Action 1.1", graphic="start.png", accelerator="ctrl+shift+T") + * private void action11() { + * System.out.println( "Action 1.1 is executed"); + * } + * } + * </pre> + * + * If you require more control over the creation of the Action objects, you can either set the + * global ActionFactory by calling ActionMap.setActionFactory() and/or you can use the factory + * property on individual @ActionProxy declarations to set the factory on a case-by-case basis. + * + * @see ActionProxy + * @see Action + */ +public class ActionMap { + + private static AnnotatedActionFactory actionFactory = new DefaultActionFactory(); + + private static final Map<String, AnnotatedAction> actions = new HashMap<>(); + + + private ActionMap() { + // no-op + } + + + /** + * Returns the action factory used by ActionMap to construct AnnotatedAction instances. By default, this + * is an instance of {@link DefaultActionFactory}. + */ + public static AnnotatedActionFactory getActionFactory() { + return actionFactory; + } + + /** + * Sets the action factory used by ActionMap to construct AnnotatedAction instances. This factory can be overridden on + * a case-by-case basis by specifying a factory class in {@link ActionProxy#factory()} + */ + public static void setActionFactory( AnnotatedActionFactory factory ) { + Objects.requireNonNull( factory ); + actionFactory = factory; + } + + + + + /** + * Attempts to convert target's methods annotated with {@link ActionProxy} to {@link Action}s. + * Three types of methods are currently converted: parameter-less methods, + * methods with one parameter of type {@link ActionEvent}, and methods with two parameters + * ({@link ActionEvent}, {@link Action}). + * + * Note that this method supports safe re-registration of a given instance or of another instance of the + * same class that has already been registered. If another instance of the same class is registered, then + * those actions will now be associated with the new instance. The first instance is implicitly unregistered. + * + * Actions are registered with their id or method name if id is not defined. + * + * @param target object to work on + * @throws IllegalStateException if a method with unsupported parameters is annotated with {@link ActionProxy}. + */ + public static void register(Object target) { + + for (Method method : target.getClass().getDeclaredMethods()) { + // Only process methods that have the ActionProxy annotation + Annotation[] annotations = method.getAnnotationsByType(ActionProxy.class); + if (annotations.length == 0) { + continue; + } + + // Only process methods that have + // a) no parameters OR + // b) one parameter of type ActionEvent OR + // c) two parameters (ActionEvent, Action) + int paramCount = method.getParameterCount(); + Class[] paramTypes = method.getParameterTypes(); + + if (paramCount > 2) { + throw new IllegalArgumentException( String.format( "Method %s has too many parameters", method.getName() ) ); + } + + if (paramCount == 1 && !ActionEvent.class.isAssignableFrom( paramTypes[0] )) { + throw new IllegalArgumentException( String.format( "Method %s -- single parameter must be of type ActionEvent", method.getName() ) ); + } + + if (paramCount == 2 && (!ActionEvent.class.isAssignableFrom( paramTypes[0] ) || + !Action.class.isAssignableFrom( paramTypes[1] ))) { + throw new IllegalArgumentException( String.format( "Method %s -- parameters must be of types (ActionEvent, Action)", method.getName() ) ); + } + + ActionProxy annotation = (ActionProxy) annotations[0]; + + AnnotatedActionFactory factory = determineActionFactory( annotation ); + AnnotatedAction action = factory.createAction( annotation, method, target ); + + String id = annotation.id().isEmpty() ? method.getName() : annotation.id(); + actions.put( id, action ); + } + } + + + + + private static AnnotatedActionFactory determineActionFactory( ActionProxy annotation ) { + // Default to using the global action factory + AnnotatedActionFactory factory = actionFactory; + + // If an action-factory has been specified on this specific ActionProxy, then + // instantiate it and use it instead. + String factoryClassName = annotation.factory(); + if (!factoryClassName.isEmpty()) { + try { + Class factoryClass = Class.forName( factoryClassName ); + factory = (AnnotatedActionFactory) factoryClass.newInstance(); + + } catch (ClassNotFoundException ex) { + throw new IllegalArgumentException( String.format( "Action proxy refers to non-existant factory class %s", factoryClassName ), ex ); + + } catch (InstantiationException | IllegalAccessException ex) { + throw new IllegalStateException( String.format( "Unable to instantiate action factory class %s", factoryClassName ), ex ); + } + } + + return factory; + } + + + /** + * Removes all the actions associated with target object from the action map. + * @param target object to work on + */ + public static void unregister(Object target) { + if ( target != null ) { + Iterator<Map.Entry<String, AnnotatedAction>> entryIter = actions.entrySet().iterator(); + while (entryIter.hasNext()) { + Map.Entry<String, AnnotatedAction> entry = entryIter.next(); + + Object actionTarget = entry.getValue().getTarget(); + + if (actionTarget == null || actionTarget == target) { + entryIter.remove(); + } + } + } + } + + /** + * Returns action by its id. + * @param id action id + * @return action or null if id was not found + */ + public static Action action(String id) { + return actions.get(id); + } + + /** + * Returns collection of actions by ids. Useful to create {@link ActionGroup}s. + * Ids starting with "---" are converted to {@link ActionUtils#ACTION_SEPARATOR}. + * Incorrect ids are ignored. + * @param ids action ids + * @return collection of actions + */ + public static Collection<Action> actions(String... ids) { + List<Action> result = new ArrayList<>(); + for( String id: ids ) { + if ( id.startsWith("---")) result.add(ActionUtils.ACTION_SEPARATOR); //$NON-NLS-1$ + Action action = action(id); + if ( action != null ) result.add(action); + } + return result; + } +} diff --git a/src/org/controlsfx/control/action/ActionProxy.java b/src/org/controlsfx/control/action/ActionProxy.java new file mode 100644 index 0000000000000000000000000000000000000000..30f9064f36740a0f94240055b5389131c0f9acdc --- /dev/null +++ b/src/org/controlsfx/control/action/ActionProxy.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import javafx.event.ActionEvent; +import org.controlsfx.glyphfont.Glyph; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to allow conversion of class methods to {@link Action} instances. + * + * <p>The following steps are required to use {@link ActionProxy} annotations: + * + * <ol> + * <li>Annotate your methods with the {@link ActionProxy} annotation. For example: + * <pre>{@code @ActionProxy(text="Action 1.1", graphic=imagePath, accelerator="ctrl+shift+T") + * private void action11() { + * System.out.println("Action 1.1 is executed"); + * }}</pre> + * + * <p>The ActionProxy annotation is designed to work with three types of methods: + * <ol> + * <li>Methods with no parameters, + * <li>Methods with one parameter of type {@link ActionEvent}. + * <li>Methods that take both an {@link ActionEvent} and an {@link Action}. + * </ol> + * + * <p>The ActionProxy annotation {@link #graphic()} property supports different node types: + * <ol> + * <li>Images, + * <li>Glyph fonts. + * </ol> + * + * <p>The ability for ActionProxy to support glyph fonts is part of the ControlsFX + * {@link Glyph} API. For more information on how to specify + * images and glyph fonts, refer to the {@link ActionProxy#graphic()} method. + * <br><br></li> + * + * <li>Register your class in the global {@link ActionMap}, preferably in the + * class constructor: + * <pre>{@code ActionMap.register(this); }</pre> + * + * Immediately after that actions will be created according to the provided + * annotations and are accessible from {@link ActionMap}, which provides several + * convenience methods to access actions by id. Refer to the {@link ActionMap} + * class for more details on how to use it.</li> + * + * <p>{@link ActionCheck} annotation is supported on the same method where @ActionProxy is applied}</p> + * </ol> + * + * @see Action + * @see ActionMap + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ActionProxy { + + /** + * By default the method name that this annotation is applied to, but if not + * null then this ID is what you use when requesting the {@link Action} out + * of the {@link ActionMap} when using the {@link ActionMap#action(String)} + * method. + */ + String id() default ""; + + /** + * The text that should be set in {@link Action#textProperty()}. + */ + String text(); + + /** + * The graphic that should be set in {@link Action#graphicProperty()}. + * + * <p>The graphic can be either image (local path or url) or font glyph. + * + * <p>Because a graphic can come from multiple sources, a simple protocol + * prefix is used to designate the type. Currently supported prefixes are + * '<code>image></code>' and '<code>font></code>'. Default protocol is + * '<code>image></code>'. + * + * <p>The following are the examples of different graphic nodes: + * <pre> + * @ActionProxy(text="Teacher", graphic="http://icons.iconarchive.com/icons/custom-icon-design/mini-3/16/teacher-male-icon.png") + * @ActionProxy(text="Security", graphic="/org/controlsfx/samples/security-low.png") + * @ActionProxy(text="Security", graphic="image>/org/controlsfx/samples/security-low.png") + * @ActionProxy(text="Star", graphic="font>FontAwesome|STAR") + * </pre> + * + */ + String graphic() default ""; + + /** + * The text that should be set in {@link Action#longTextProperty()}. + */ + String longText() default ""; + + /** + * Accepts string values such as "ctrl+shift+T" to represent the keyboard + * shortcut for this action. By default this is empty if there is no keyboard + * shortcut desired for this action. + */ + String accelerator() default ""; + + /** + * The full class-name of a class that implements {@link AnnotatedActionFactory}. {@link ActionMap} will + * use this class to instantiate the {@link AnnotatedAction} associated with this method, rather than + * using its own action factory. + */ + String factory() default ""; +} diff --git a/src/org/controlsfx/control/action/ActionUtils.java b/src/org/controlsfx/control/action/ActionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d7169dcdb863cc45d335fa2cdbe2f25bfa2e4715 --- /dev/null +++ b/src/org/controlsfx/control/action/ActionUtils.java @@ -0,0 +1,890 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import javafx.beans.InvalidationListener; +import javafx.beans.binding.ObjectBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.binding.When; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableList; +import javafx.css.Styleable; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.controlsfx.control.SegmentedButton; +import org.controlsfx.tools.Duplicatable; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +/** + * Convenience class for users of the {@link Action} API. Primarily this class + * is used to conveniently create UI controls from a given Action (this is + * necessary for now as there is no built-in support for Action in JavaFX + * UI controls at present). + * + * <p>Some of the methods in this class take a {@link Collection} of + * {@link Action actions}. In these cases, it is likely they are designed to + * work with {@link ActionGroup action groups}. For examples on how to work with + * these methods, refer to the {@link ActionGroup} class documentation. + * + * @see Action + * @see ActionGroup + */ +@SuppressWarnings("deprecation") +public class ActionUtils { + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + private ActionUtils() { + // no-op + } + + /*************************************************************************** + * * + * Action API * + * * + **************************************************************************/ + + /** + * Action text behavior. + * Defines uniform action's text behavior for multi-action controls such as toolbars and menus + */ + public enum ActionTextBehavior { + /** + * Text is shown as usual on related control + */ + SHOW, + + /** + * Text is not shown on the related control + */ + HIDE, + } + + + /** + * Takes the provided {@link Action} and returns a {@link Button} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link Button} should bind to. + * @param textBehavior Defines {@link ActionTextBehavior} + * @return A {@link Button} that is bound to the state of the provided + * {@link Action} + */ + public static Button createButton(final Action action, final ActionTextBehavior textBehavior) { + return configure(new Button(), action, textBehavior); + } + + /** + * Takes the provided {@link Action} and returns a {@link Button} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link Button} should bind to. + * @return A {@link Button} that is bound to the state of the provided + * {@link Action} + */ + public static Button createButton(final Action action) { + return configure(new Button(), action, ActionTextBehavior.SHOW); + } + + /** + * Takes the provided {@link Action} and binds the relevant properties to + * the supplied {@link Button}. This allows for the use of Actions + * within custom Button subclasses. + * + * @param action The {@link Action} that the {@link Button} should bind to. + * @param button The {@link ButtonBase} that the {@link Action} should be bound to. + * @return The {@link ButtonBase} that was bound to the {@link Action}. + */ + public static ButtonBase configureButton(final Action action, ButtonBase button) { + return configure(button, action, ActionTextBehavior.SHOW); + } + + /** + * Removes all bindings and listeners which were added when the supplied + * {@link ButtonBase} was bound to an {@link Action} via one of the methods + * of this class. + * + * @param button a {@link ButtonBase} that was bound to an {@link Action} + */ + public static void unconfigureButton(ButtonBase button) { + unconfigure(button); + } + + /** + * Takes the provided {@link Action} and returns a {@link MenuButton} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link MenuButton} should bind to. + * @param textBehavior Defines {@link ActionTextBehavior} + * @return A {@link MenuButton} that is bound to the state of the provided + * {@link Action} + */ + public static MenuButton createMenuButton(final Action action, final ActionTextBehavior textBehavior) { + return configure(new MenuButton(), action, textBehavior); + } + + /** + * Takes the provided {@link Action} and returns a {@link MenuButton} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link MenuButton} should bind to. + * @return A {@link MenuButton} that is bound to the state of the provided + * {@link Action} + */ + public static MenuButton createMenuButton(final Action action) { + return configure(new MenuButton(), action, ActionTextBehavior.SHOW); + } + + /** + * Takes the provided {@link Action} and returns a {@link Hyperlink} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link Hyperlink} should bind to. + * @return A {@link Hyperlink} that is bound to the state of the provided + * {@link Action} + */ + public static Hyperlink createHyperlink(final Action action) { + return configure(new Hyperlink(), action, ActionTextBehavior.SHOW); + } + + /** + * Takes the provided {@link Action} and returns a {@link ToggleButton} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link ToggleButton} should bind to. + * @param textBehavior Defines {@link ActionTextBehavior} + * @return A {@link ToggleButton} that is bound to the state of the provided + * {@link Action} + */ + public static ToggleButton createToggleButton(final Action action, final ActionTextBehavior textBehavior ) { + return configure(new ToggleButton(), action, textBehavior); + } + + /** + * Takes the provided {@link Action} and returns a {@link ToggleButton} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link ToggleButton} should bind to. + * @return A {@link ToggleButton} that is bound to the state of the provided + * {@link Action} + */ + public static ToggleButton createToggleButton( final Action action ) { + return createToggleButton( action, ActionTextBehavior.SHOW ); + } + + /** + * Takes the provided {@link Collection} of {@link Action} and returns a {@link SegmentedButton} instance + * with all relevant properties bound to the properties of the actions. + * + * @param actions The {@link Collection} of {@link Action} that the {@link SegmentedButton} should bind to. + * @param textBehavior Defines {@link ActionTextBehavior} + * @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s + */ + public static SegmentedButton createSegmentedButton(final ActionTextBehavior textBehavior, Collection<? extends Action> actions) { + ObservableList<ToggleButton> buttons = FXCollections.observableArrayList(); + for( Action a: actions ) { + buttons.add( createToggleButton(a,textBehavior)); + } + return new SegmentedButton( buttons ); + } + + /** + * Takes the provided {@link Collection} of {@link Action} and returns a {@link SegmentedButton} instance + * with all relevant properties bound to the properties of the actions. + * + * @param actions The {@link Collection} of {@link Action} that the {@link SegmentedButton} should bind to. + * @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s + */ + public static SegmentedButton createSegmentedButton(Collection<? extends Action> actions) { + return createSegmentedButton( ActionTextBehavior.SHOW, actions); + } + + /** + * Takes the provided varargs array of {@link Action} and returns a {@link SegmentedButton} instance + * with all relevant properties bound to the properties of the actions. + * + * @param actions A varargs array of {@link Action} that the {@link SegmentedButton} should bind to. + * @param textBehavior Defines {@link ActionTextBehavior} + * @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s + */ + public static SegmentedButton createSegmentedButton(ActionTextBehavior textBehavior, Action... actions) { + return createSegmentedButton(textBehavior, Arrays.asList(actions)); + } + + /** + * Takes the provided varargs array of {@link Action} and returns a {@link SegmentedButton} instance + * with all relevant properties bound to the properties of the actions. + * + * @param actions A varargs array of {@link Action} that the {@link SegmentedButton} should bind to. + * @return A {@link SegmentedButton} that is bound to the state of the provided {@link Action}s + */ + public static SegmentedButton createSegmentedButton(Action... actions) { + return createSegmentedButton(ActionTextBehavior.SHOW, Arrays.asList(actions)); + } + + + + /** + * Takes the provided {@link Action} and returns a {@link CheckBox} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link CheckBox} should bind to. + * @return A {@link CheckBox} that is bound to the state of the provided + * {@link Action} + */ + public static CheckBox createCheckBox(final Action action) { + return configure(new CheckBox(), action, ActionTextBehavior.SHOW); + } + + /** + * Takes the provided {@link Action} and returns a {@link RadioButton} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link RadioButton} should bind to. + * @return A {@link RadioButton} that is bound to the state of the provided + * {@link Action} + */ + public static RadioButton createRadioButton(final Action action) { + return configure(new RadioButton(), action, ActionTextBehavior.SHOW); + } + + /** + * Takes the provided {@link Action} and returns a {@link MenuItem} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link MenuItem} should bind to. + * @return A {@link MenuItem} that is bound to the state of the provided + * {@link Action} + */ + public static MenuItem createMenuItem(final Action action) { + + MenuItem menuItem = + action.getClass().isAnnotationPresent(ActionCheck.class)? new CheckMenuItem(): new MenuItem(); + + return configure( menuItem, action); + } + + public static MenuItem configureMenuItem(final Action action, MenuItem menuItem) { + return configure(menuItem, action); + } + + /** + * Removes all bindings and listeners which were added when the supplied + * {@link MenuItem} was bound to an {@link Action} via one of the methods + * of this class. + * + * @param menuItem a {@link MenuItem} that was bound to an {@link Action} + */ + public static void unconfigureMenuItem(final MenuItem menuItem) { + unconfigure(menuItem); + } + + /** + * Takes the provided {@link Action} and returns a {@link Menu} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link Menu} should bind to. + * @return A {@link Menu} that is bound to the state of the provided + * {@link Action} + */ + public static Menu createMenu(final Action action) { + return configure(new Menu(), action); + } + + /** + * Takes the provided {@link Action} and returns a {@link CheckMenuItem} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link CheckMenuItem} should bind to. + * @return A {@link CheckMenuItem} that is bound to the state of the provided + * {@link Action} + */ + public static CheckMenuItem createCheckMenuItem(final Action action) { + return configure(new CheckMenuItem(), action); + } + + /** + * Takes the provided {@link Action} and returns a {@link RadioMenuItem} instance + * with all relevant properties bound to the properties of the Action. + * + * @param action The {@link Action} that the {@link RadioMenuItem} should bind to. + * @return A {@link RadioMenuItem} that is bound to the state of the provided + * {@link Action} + */ + public static RadioMenuItem createRadioMenuItem(final Action action) { + return configure(new RadioMenuItem(action.textProperty().get()), action); + } + + + + /*************************************************************************** + * * + * ActionGroup API * + * * + **************************************************************************/ + + + /** + * Action representation of the generic separator. Adding this action anywhere in the + * action tree serves as indication that separator has be created in its place. + * See {@link ActionGroup} for example of action tree creation + */ + public static Action ACTION_SEPARATOR = new Action(null, null) { + @Override public String toString() { + return "Separator"; //$NON-NLS-1$ + } + }; + + public static Action ACTION_SPAN = new Action(null, null) { + @Override public String toString() { + return "Span"; //$NON-NLS-1$ + } + }; + + + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and returns a {@link ToolBar} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. + * + * @param actions The {@link Action actions} to place on the {@link ToolBar}. + * @param textBehavior defines {@link ActionTextBehavior} + * @return A {@link ToolBar} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static ToolBar createToolBar(Collection<? extends Action> actions, ActionTextBehavior textBehavior) { + return updateToolBar( new ToolBar(), actions, textBehavior ); + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and returns provided {@link ToolBar} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. Previous toolbar content is removed + * + * @param toolbar The {@link ToolBar toolbar} to update + * @param actions The {@link Action actions} to place on the {@link ToolBar}. + * @param textBehavior defines {@link ActionTextBehavior} + * @return A {@link ToolBar} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static ToolBar updateToolBar( ToolBar toolbar, Collection<? extends Action> actions, ActionTextBehavior textBehavior) { + toolbar.getItems().clear(); + for (Action action : actions) { + if ( action instanceof ActionGroup ) { + MenuButton menu = createMenuButton( action, textBehavior ); + menu.setFocusTraversable(false); + menu.getItems().addAll( toMenuItems( ((ActionGroup)action).getActions())); + toolbar.getItems().add(menu); + } else if ( action == ACTION_SEPARATOR ) { + toolbar.getItems().add( new Separator()); + } else if ( action == ACTION_SPAN ) { + Pane span = new Pane(); + HBox.setHgrow(span, Priority.ALWAYS); + VBox.setVgrow(span, Priority.ALWAYS); + toolbar.getItems().add(span); + } else if ( action == null ) { + //no-op + } else { + + ButtonBase button; + if ( action.getClass().getAnnotation(ActionCheck.class) != null ) { + button = createToggleButton(action, textBehavior); + } else { + button = createButton(action, textBehavior); + } + button.setFocusTraversable(false); + toolbar.getItems().add(button); + } + } + + return toolbar; + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and returns a {@link MenuBar} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. + * + * @param actions The {@link Action actions} to place on the {@link MenuBar}. + * @return A {@link MenuBar} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static MenuBar createMenuBar(Collection<? extends Action> actions) { + return updateMenuBar(new MenuBar(), actions); + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and updates a {@link MenuBar} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. Previous MenuBar content is removed. + * + * @param menuBar The {@link MenuBar menuBar} to update + * @param actions The {@link Action actions} to place on the {@link MenuBar}. + * @return A {@link MenuBar} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static MenuBar updateMenuBar( MenuBar menuBar, Collection<? extends Action> actions) { + menuBar.getMenus().clear(); + for (Action action : actions) { + + if ( action == ACTION_SEPARATOR || action == ACTION_SPAN ) continue; + + Menu menu = createMenu( action ); + + if ( action instanceof ActionGroup ) { + menu.getItems().addAll( toMenuItems( ((ActionGroup)action).getActions())); + } else if ( action == null ) { + //no-op + } + + menuBar.getMenus().add(menu); + } + + return menuBar; + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and returns a {@link ButtonBar} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. + * + * @param actions The {@link Action actions} to place on the {@link ButtonBar}. + * @return A {@link ButtonBar} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static ButtonBar createButtonBar(Collection<? extends Action> actions) { + return updateButtonBar( new ButtonBar(), actions); + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and updates a {@link ButtonBar} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. Previous content of button bar is removed + * + * @param buttonBar The {@link ButtonBar buttonBar} to update + * @param actions The {@link Action actions} to place on the {@link ButtonBar}. + * @return A {@link ButtonBar} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static ButtonBar updateButtonBar( ButtonBar buttonBar, Collection<? extends Action> actions) { + buttonBar.getButtons().clear(); + for (Action action : actions) { + if ( action instanceof ActionGroup ) { + // no-op + } else if ( action == ACTION_SPAN || action == ACTION_SEPARATOR || action == null ) { + // no-op + } else { + buttonBar.getButtons().add(createButton(action, ActionTextBehavior.SHOW)); + } + } + + return buttonBar; + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and returns a {@link ContextMenu} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. + * + * @param actions The {@link Action actions} to place on the {@link ContextMenu}. + * @return A {@link ContextMenu} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static ContextMenu createContextMenu(Collection<? extends Action> actions) { + return updateContextMenu(new ContextMenu(), actions); + } + + /** + * Takes the provided {@link Collection} of {@link Action} (or subclasses, + * such as {@link ActionGroup}) instances and updates a {@link ContextMenu} + * populated with appropriate {@link Node nodes} bound to the provided + * {@link Action actions}. Previous content of context menu is removed + * + * @param menu The {@link ContextMenu menu} to update + * @param actions The {@link Action actions} to place on the {@link ContextMenu}. + * @return A {@link ContextMenu} that contains {@link Node nodes} which are bound + * to the state of the provided {@link Action} + */ + public static ContextMenu updateContextMenu(ContextMenu menu, Collection<? extends Action> actions) { + menu.getItems().clear(); + menu.getItems().addAll(toMenuItems(actions)); + return menu; + } + + + /*************************************************************************** + * * + * Private implementation * + * * + **************************************************************************/ + + private static Collection<MenuItem> toMenuItems( Collection<? extends Action> actions ) { + + Collection<MenuItem> items = new ArrayList<>(); + + for (Action action : actions) { + + if ( action instanceof ActionGroup ) { + + Menu menu = createMenu( action ); + menu.getItems().addAll( toMenuItems( ((ActionGroup)action).getActions())); + items.add(menu); + + } else if ( action == ACTION_SEPARATOR ) { + + items.add( new SeparatorMenuItem()); + + } else if ( action == null || action == ACTION_SPAN) { + // no-op + } else { + + items.add( createMenuItem(action)); + + } + + } + + return items; + + } + + private static Node copyNode( Node node ) { + if ( node instanceof ImageView ) { + return new ImageView( ((ImageView)node).getImage()); + } else if ( node instanceof Duplicatable<?> ) { + return (Node) ((Duplicatable<?>)node).duplicate(); + } else { + return null; + } + } + + // Carry over action style classes changes to the styleable + // Binding as not a good solution since it wipes out existing styleable classes + private static void bindStyle(final Styleable styleable, final Action action ) { + styleable.getStyleClass().addAll( action.getStyleClass() ); + action.getStyleClass().addListener(new ListChangeListener<String>() { + @Override + public void onChanged(Change<? extends String> c) { + while(c.next()) { + if (c.wasRemoved()) { + styleable.getStyleClass().removeAll(c.getRemoved()); + } + if (c.wasAdded()) { + styleable.getStyleClass().addAll(c.getAddedSubList()); + } + } + } + }); + } + + private static <T extends ButtonBase> T configure(final T btn, final Action action, final ActionTextBehavior textBehavior) { + if (action == null) { + throw new NullPointerException("Action can not be null"); //$NON-NLS-1$ + } + + // button bind to action properties + + bindStyle(btn,action); + + //btn.textProperty().bind(action.textProperty()); + if ( textBehavior == ActionTextBehavior.SHOW ) { + btn.textProperty().bind(action.textProperty()); + } + btn.disableProperty().bind(action.disabledProperty()); + + + btn.graphicProperty().bind(new ObjectBinding<Node>() { + { bind(action.graphicProperty()); } + + @Override protected Node computeValue() { + return copyNode(action.graphicProperty().get()); + } + + @Override + public void removeListener(InvalidationListener listener) { + super.removeListener(listener); + unbind(action.graphicProperty()); + } + }); + + + // add all the properties of the action into the button, and set up + // a listener so they are always copied across + btn.getProperties().putAll(action.getProperties()); + action.getProperties().addListener(new ButtonPropertiesMapChangeListener<>(btn, action)); + + // tooltip requires some special handling (i.e. don't have one when + // the text property is null + btn.tooltipProperty().bind(new ObjectBinding<Tooltip>() { + private Tooltip tooltip = new Tooltip(); + private StringBinding textBinding = new When(action.longTextProperty().isEmpty()).then(action.textProperty()).otherwise(action.longTextProperty()); + + { + bind(textBinding); + tooltip.textProperty().bind(textBinding); + } + + @Override protected Tooltip computeValue() { + String longText = textBinding.get(); + return longText == null || textBinding.get().isEmpty() ? null : tooltip; + } + + @Override + public void removeListener(InvalidationListener listener) { + super.removeListener(listener); + unbind(action.longTextProperty()); + tooltip.textProperty().unbind(); + } + }); + + + + // Handle the selected state of the button if it is of the applicable type + + if ( btn instanceof ToggleButton ) { + ((ToggleButton)btn).selectedProperty().bindBidirectional(action.selectedProperty()); + } + + // Just call the execute method on the action itself when the action + // event occurs on the button + btn.setOnAction(action); + + return btn; + } + + private static void unconfigure(final ButtonBase btn) { + if (btn == null || !(btn.getOnAction() instanceof Action)) { + return; + } + + Action action = (Action) btn.getOnAction(); + + btn.styleProperty().unbind(); + btn.textProperty().unbind(); + btn.disableProperty().unbind(); + btn.graphicProperty().unbind(); + + action.getProperties().removeListener(new ButtonPropertiesMapChangeListener<>(btn, action)); + + btn.tooltipProperty().unbind(); + + if (btn instanceof ToggleButton) { + ((ToggleButton) btn).selectedProperty().unbindBidirectional(action.selectedProperty()); + } + + btn.setOnAction(null); + } + + private static <T extends MenuItem> T configure(final T menuItem, final Action action) { + if (action == null) { + throw new NullPointerException("Action can not be null"); //$NON-NLS-1$ + } + + // button bind to action properties + bindStyle(menuItem,action); + + menuItem.textProperty().bind(action.textProperty()); + menuItem.disableProperty().bind(action.disabledProperty()); + menuItem.acceleratorProperty().bind(action.acceleratorProperty()); + + menuItem.graphicProperty().bind(new ObjectBinding<Node>() { + { bind(action.graphicProperty()); } + + @Override protected Node computeValue() { + return copyNode( action.graphicProperty().get()); + } + + @Override + public void removeListener(InvalidationListener listener) { + super.removeListener(listener); + unbind(action.graphicProperty()); + } + }); + + + // add all the properties of the action into the button, and set up + // a listener so they are always copied across + menuItem.getProperties().putAll(action.getProperties()); + action.getProperties().addListener(new MenuItemPropertiesMapChangeListener<>(menuItem, action)); + + // Handle the selected state of the menu item if it is a + // CheckMenuItem or RadioMenuItem + + if ( menuItem instanceof RadioMenuItem ) { + ((RadioMenuItem)menuItem).selectedProperty().bindBidirectional(action.selectedProperty()); + } else if ( menuItem instanceof CheckMenuItem ) { + ((CheckMenuItem)menuItem).selectedProperty().bindBidirectional(action.selectedProperty()); + } + + // Just call the execute method on the action itself when the action + // event occurs on the button + menuItem.setOnAction(action); + + return menuItem; + } + + private static void unconfigure(final MenuItem menuItem) { + if (menuItem == null || !(menuItem.getOnAction() instanceof Action)) { + return; + } + + Action action = (Action) menuItem.getOnAction(); + + menuItem.styleProperty().unbind(); + menuItem.textProperty().unbind(); + menuItem.disableProperty().unbind(); + menuItem.acceleratorProperty().unbind(); + menuItem.graphicProperty().unbind(); + + action.getProperties().removeListener(new MenuItemPropertiesMapChangeListener<>(menuItem, action)); + + if (menuItem instanceof RadioMenuItem) { + ((RadioMenuItem) menuItem).selectedProperty().unbindBidirectional(action.selectedProperty()); + } else if (menuItem instanceof CheckMenuItem) { + ((CheckMenuItem) menuItem).selectedProperty().unbindBidirectional(action.selectedProperty()); + } + + menuItem.setOnAction(null); + } + + private static class ButtonPropertiesMapChangeListener<T extends ButtonBase> implements MapChangeListener<Object, Object> { + + private final WeakReference<T> btnWeakReference; + private final Action action; + + private ButtonPropertiesMapChangeListener(T btn, Action action) { + btnWeakReference = new WeakReference<>(btn); + this.action = action; + } + + @Override public void onChanged(MapChangeListener.Change<?, ?> change) { + T btn = btnWeakReference.get(); + if (btn == null) { + action.getProperties().removeListener(this); + } else { + btn.getProperties().clear(); + btn.getProperties().putAll(action.getProperties()); + } + } + + @Override + public boolean equals(Object otherObject) { + if (this == otherObject) { + return true; + } + if (otherObject == null || getClass() != otherObject.getClass()) { + return false; + } + + ButtonPropertiesMapChangeListener<?> otherListener = (ButtonPropertiesMapChangeListener<?>) otherObject; + + T btn = btnWeakReference.get(); + ButtonBase otherBtn = otherListener.btnWeakReference.get(); + if (btn != null ? !btn.equals(otherBtn) : otherBtn != null) { + return false; + } + return action.equals(otherListener.action); + } + + @Override + public int hashCode() { + T btn = btnWeakReference.get(); + int result = btn != null ? btn.hashCode() : 0; + result = 31 * result + action.hashCode(); + return result; + } + } + + private static class MenuItemPropertiesMapChangeListener<T extends MenuItem> implements MapChangeListener<Object, Object> { + + private final WeakReference<T> menuItemWeakReference; + private final Action action; + + private MenuItemPropertiesMapChangeListener(T menuItem, Action action) { + menuItemWeakReference = new WeakReference<>(menuItem); + this.action = action; + } + + @Override public void onChanged(MapChangeListener.Change<?, ?> change) { + T menuItem = menuItemWeakReference.get(); + if (menuItem == null) { + action.getProperties().removeListener(this); + } else { + menuItem.getProperties().clear(); + menuItem.getProperties().putAll(action.getProperties()); + } + } + + @Override + public boolean equals(Object otherObject) { + if (this == otherObject) { + return true; + } + if (otherObject == null || getClass() != otherObject.getClass()) { + return false; + } + + MenuItemPropertiesMapChangeListener<?> otherListener = (MenuItemPropertiesMapChangeListener<?>) otherObject; + + T menuItem = menuItemWeakReference.get(); + MenuItem otherMenuItem = otherListener.menuItemWeakReference.get(); + return menuItem != null ? menuItem.equals(otherMenuItem) : otherMenuItem == null && action.equals(otherListener.action); + + } + + @Override + public int hashCode() { + T menuItem = menuItemWeakReference.get(); + int result = menuItem != null ? menuItem.hashCode() : 0; + result = 31 * result + action.hashCode(); + return result; + } + } +} diff --git a/src/org/controlsfx/control/action/AnnotatedAction.java b/src/org/controlsfx/control/action/AnnotatedAction.java new file mode 100644 index 0000000000000000000000000000000000000000..b667cda1f52462dec26f2fab262e76d9ce05d797 --- /dev/null +++ b/src/org/controlsfx/control/action/AnnotatedAction.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.Objects; +import javafx.event.ActionEvent; + + + +/** + * An action that invokes a method that has been annotated with {@link ActionProxy}. These actions are created via + * {@link ActionMap#register(java.lang.Object)}, which delegates the actual instantiation to an {@link AnnotatedActionFactory}. + * + * Note that this class maintains a WeakReference to the supplied target object, so the existence of an + * AnnotatedAction instance will not prevent the target from being garbage-collected. + */ +public class AnnotatedAction extends Action { + + private final Method method; + private final WeakReference<Object> target; + + /** + * Instantiates an action that will call the specified method on the specified target. + */ + public AnnotatedAction(String text, Method method, Object target) { + super(text); + Objects.requireNonNull( method ); + Objects.requireNonNull( target ); + + setEventHandler(this::handleAction); + + this.method = method; + this.method.setAccessible(true); + this.target = new WeakReference( target ); + } + + /** + * Returns the target object (the object on which the annotated method will be called). + * + * @return The target object, or null if the target object has been garbage-collected. + */ + public Object getTarget() { + return target.get(); + } + + /** + * Handle the action-event by invoking the annotated method on the target object. If an exception is + * thrown, then the default implementation of this method will call handleActionException(). + */ + protected void handleAction(ActionEvent ae) { + try { + Object actionTarget = getTarget(); + if (actionTarget == null) { + throw new IllegalStateException( "Action target object is no longer reachable" ); + } + + int paramCount = method.getParameterCount(); + if ( paramCount == 0 ) { + method.invoke(actionTarget); + + } else if ( paramCount == 1) { + method.invoke(actionTarget, ae); + + } else if ( paramCount == 2) { + method.invoke(actionTarget, ae, this); + } + } catch (Throwable e) { + handleActionException( ae, e ); + } + } + + + /** + * Called if the annotated method throws an exception when invoked. The default implementation of this method simply prints + * the stack trace of the specified exception. + */ + protected void handleActionException( ActionEvent ae, Throwable ex ) { + ex.printStackTrace(); + } + + + /** + * Overridden to return the text of this action. + */ + @Override + public String toString() { + return getText(); + } +} diff --git a/src/org/controlsfx/control/action/AnnotatedActionFactory.java b/src/org/controlsfx/control/action/AnnotatedActionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..9c083de16a08e2d11b39714ba5b965e3caa575d4 --- /dev/null +++ b/src/org/controlsfx/control/action/AnnotatedActionFactory.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import java.lang.reflect.Method; + + +/** + * Defines the interface used by {@link ActionMap} for creating instances of {@link AnnotatedAction}. + */ +public interface AnnotatedActionFactory { + + /** + * Create an {@link AnnotatedAction} instance. + * + * @param annotation The annotation specified on the method. + * @param method The method to be invoked when an action is fired. + * @param target The target object on which the method will be invoked. + * @return An {@link AnnotatedAction} instance. + */ + AnnotatedAction createAction( ActionProxy annotation, Method method, Object target ); +} diff --git a/src/org/controlsfx/control/action/AnnotatedCheckAction.java b/src/org/controlsfx/control/action/AnnotatedCheckAction.java new file mode 100644 index 0000000000000000000000000000000000000000..5b5dfd390a3afb4dcc8645112378b90ebbd71586 --- /dev/null +++ b/src/org/controlsfx/control/action/AnnotatedCheckAction.java @@ -0,0 +1,19 @@ +package org.controlsfx.control.action; + +import java.lang.reflect.Method; + +@ActionCheck +public class AnnotatedCheckAction extends AnnotatedAction { + + /** + * Instantiates an action that will call the specified method on the specified target. + * This action is marked with @ActionCheck + * + * @param text + * @param method + * @param target + */ + public AnnotatedCheckAction(String text, Method method, Object target) { + super(text, method, target); + } +} diff --git a/src/org/controlsfx/control/action/DefaultActionFactory.java b/src/org/controlsfx/control/action/DefaultActionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..3dc3d3199b37428efa06a5f20a3f787f870e198a --- /dev/null +++ b/src/org/controlsfx/control/action/DefaultActionFactory.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.action; + +import javafx.scene.Node; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCombination; +import org.controlsfx.glyphfont.Glyph; + +import java.lang.reflect.Method; + +/** + * The default {@link AnnotatedActionFactory} to be used when no alternative has been specified. This class creates + * instances of {@link AnnotatedAction}. + */ +public class DefaultActionFactory implements AnnotatedActionFactory { + + /** + * Create an {@link AnnotatedAction}. This method is called by {@link ActionMap#register(java.lang.Object)}. + * + * @param annotation The annotation specified on the method. + * @param method The method to be invoked when an action is fired. + * @param target The target object on which the method will be invoked. + * @return An {@link AnnotatedAction} instance. + */ + @Override + public AnnotatedAction createAction( ActionProxy annotation, Method method, Object target ) { + AnnotatedAction action; + if ( method.isAnnotationPresent(ActionCheck.class)) { + action = new AnnotatedCheckAction(annotation.text(), method, target); + } else { + action = new AnnotatedAction(annotation.text(), method, target); + } + + configureAction( annotation, action ); + + return action; + } + + + /** + * Configures the newly-created action before it is returned to {@link ActionMap}. Subclasses can override this method + * to change configuration behavior. + * + * @param annotation The annotation specified on the method. + * @param action The newly-created action. + */ + protected void configureAction( ActionProxy annotation, AnnotatedAction action ) { + Node graphic = resolveGraphic(annotation); + action.setGraphic(graphic); + + // set long text / tooltip + String longText = annotation.longText().trim(); + if ( graphic != null ) { + action.setLongText(longText); + } + + // set accelerator + String acceleratorText = annotation.accelerator().trim(); + if (!acceleratorText.isEmpty()) { + action.setAccelerator(KeyCombination.keyCombination(acceleratorText)); + } + + } + + + /** + * Resolve the graphical representation of this action. The default implementation of this method implements the protocol described + * in {@link ActionProxy#graphic()}, but subclasses can override this method to provide alternative behavior. + * + * @param annotation The annotation specified on the method. + * @return A JavaFX Node for the graphic associated with this action. + */ + protected Node resolveGraphic( ActionProxy annotation ) { + String graphicDef = annotation.graphic().trim(); + if ( !graphicDef.isEmpty()) { + + String[] def = graphicDef.split("\\>"); // cannot use ':' because it used in urls //$NON-NLS-1$ + if ( def.length == 1 ) return new ImageView(new Image(def[0])); + switch (def[0]) { + case "font" : return Glyph.create(def[1]); //$NON-NLS-1$ + case "image" : return new ImageView(new Image(def[1])); //$NON-NLS-1$ + default: throw new IllegalArgumentException( String.format("Unknown ActionProxy graphic protocol: %s", def[0])); //$NON-NLS-1$ + } + } + return null; + } + +} diff --git a/src/org/controlsfx/control/action/package-info.java b/src/org/controlsfx/control/action/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..bcb828d425e564dfcde8f989a9faac66410fa1a3 --- /dev/null +++ b/src/org/controlsfx/control/action/package-info.java @@ -0,0 +1,7 @@ +/** + * A package containing the {@link org.controlsfx.control.action.Action} API, as well + * as the {@link org.controlsfx.control.action.Action} convenience subclass. + * Refer to these two classes for the necessary details on what actions are in + * the JavaFX context. + */ +package org.controlsfx.control.action; \ No newline at end of file diff --git a/src/org/controlsfx/control/breadcrumbbar.css b/src/org/controlsfx/control/breadcrumbbar.css new file mode 100644 index 0000000000000000000000000000000000000000..18ba30718467255358c4d4dab15b24b498d71c76 --- /dev/null +++ b/src/org/controlsfx/control/breadcrumbbar.css @@ -0,0 +1,3 @@ +.bread-crumb-bar { + +} diff --git a/src/org/controlsfx/control/cell/ColorGridCell.java b/src/org/controlsfx/control/cell/ColorGridCell.java new file mode 100644 index 0000000000000000000000000000000000000000..7317ccf75397d3363ad8aa8d3448ecdf0ce62b2b --- /dev/null +++ b/src/org/controlsfx/control/cell/ColorGridCell.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.cell; + +import javafx.scene.control.ContentDisplay; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; + +import org.controlsfx.control.GridCell; +import org.controlsfx.control.GridView; + +/** + * A {@link GridCell} that can be used to show coloured rectangles inside the + * {@link GridView} control. + * + * @see GridView + */ +public class ColorGridCell extends GridCell<Color> { + + private Rectangle colorRect; + + private static final boolean debug = false; + + /** + * Creates a default ColorGridCell instance. + */ + public ColorGridCell() { + getStyleClass().add("color-grid-cell"); //$NON-NLS-1$ + + colorRect = new Rectangle(); + colorRect.setStroke(Color.BLACK); + colorRect.heightProperty().bind(heightProperty()); + colorRect.widthProperty().bind(widthProperty()); + setGraphic(colorRect); + + if (debug) { + setContentDisplay(ContentDisplay.TEXT_ONLY); + } + } + + /** + * {@inheritDoc} + */ + @Override protected void updateItem(Color item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setGraphic(null); + } else { + colorRect.setFill(item); + setGraphic(colorRect); + } + + if (debug) { + setText(getIndex() + ""); //$NON-NLS-1$ + } + } +} diff --git a/src/org/controlsfx/control/cell/ImageGridCell.java b/src/org/controlsfx/control/cell/ImageGridCell.java new file mode 100644 index 0000000000000000000000000000000000000000..7703f30ae58a40ae91c69f80fda95fef879c8a23 --- /dev/null +++ b/src/org/controlsfx/control/cell/ImageGridCell.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.cell; + +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +import org.controlsfx.control.GridCell; +import org.controlsfx.control.GridView; + +/** + * A {@link GridCell} that can be used to show images inside the + * {@link GridView} control. + * + * @see GridView + */ +public class ImageGridCell extends GridCell<Image> { + + private final ImageView imageView; + + private final boolean preserveImageProperties; + + + /** + * Creates a default ImageGridCell instance, which will preserve image properties + */ + public ImageGridCell() { + this(true); + } + + /** + * Create ImageGridCell instance + * @param preserveImageProperties if set to true will preserve image aspect ratio and smoothness + */ + public ImageGridCell( boolean preserveImageProperties ) { + getStyleClass().add("image-grid-cell"); //$NON-NLS-1$ + + this.preserveImageProperties = preserveImageProperties; + imageView = new ImageView(); + imageView.fitHeightProperty().bind(heightProperty()); + imageView.fitWidthProperty().bind(widthProperty()); + } + + /** + * {@inheritDoc} + */ + @Override protected void updateItem(Image item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setGraphic(null); + } else { + if (preserveImageProperties) { + imageView.setPreserveRatio(item.isPreserveRatio()); + imageView.setSmooth( item.isSmooth()); + } + imageView.setImage(item); + setGraphic(imageView); + } + } +} \ No newline at end of file diff --git a/src/org/controlsfx/control/cell/MediaImageCell.java b/src/org/controlsfx/control/cell/MediaImageCell.java new file mode 100644 index 0000000000000000000000000000000000000000..45648720b2910cf35d974c3f6f9061560208bd93 --- /dev/null +++ b/src/org/controlsfx/control/cell/MediaImageCell.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.cell; + +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; +import javafx.scene.media.MediaView; + +import org.controlsfx.control.GridCell; +import org.controlsfx.control.GridView; + +/** + * A {@link GridCell} that can be used to show media (i.e. movies) inside the + * {@link GridView} control. + * + * @see GridView + */ +public class MediaImageCell extends GridCell<Media> { + + private MediaPlayer mediaPlayer; + private final MediaView mediaView; + + /** + * Creates a default MediaGridCell instance. + */ + public MediaImageCell() { + getStyleClass().add("media-grid-cell"); //$NON-NLS-1$ + + mediaView = new MediaView(); + mediaView.setMediaPlayer(mediaPlayer); + mediaView.fitHeightProperty().bind(heightProperty()); + mediaView.fitWidthProperty().bind(widthProperty()); + mediaView.setMediaPlayer(mediaPlayer); + } + + /** + * Pauses the media player inside this cell. + */ + public void pause() { + if(mediaPlayer != null) { + mediaPlayer.pause(); + } + } + + /** + * Starts playing the media player inside this cell. + */ + public void play() { + if(mediaPlayer != null) { + mediaPlayer.play(); + } + } + + /** + * Stops playing the media player inside this cell. + */ + public void stop() { + if(mediaPlayer != null) { + mediaPlayer.stop(); + } + } + + /** + * {@inheritDoc} + */ + @Override protected void updateItem(Media item, boolean empty) { + super.updateItem(item, empty); + + getChildren().clear(); + if (mediaPlayer != null) { + mediaPlayer.stop(); + } + + if (empty) { + setGraphic(null); + } else { + mediaPlayer = new MediaPlayer(item); + mediaView.setMediaPlayer(mediaPlayer); + setGraphic(mediaView); + } + } +} \ No newline at end of file diff --git a/src/org/controlsfx/control/cell/package-info.java b/src/org/controlsfx/control/cell/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..430500695f7836d7a93ff3d5d33c302771cd0d3d --- /dev/null +++ b/src/org/controlsfx/control/cell/package-info.java @@ -0,0 +1,9 @@ +/** + * A package containing a number of useful cell-related classes that do not + * exist in the base JavaFX distribution, many related to the new + * {@link org.controlsfx.control.GridView GridView} control offered in + * ControlsFX. + * + * @see org.controlsfx.control.GridView + */ +package org.controlsfx.control.cell; \ No newline at end of file diff --git a/src/org/controlsfx/control/collapse.png b/src/org/controlsfx/control/collapse.png new file mode 100644 index 0000000000000000000000000000000000000000..b8896c70d7c216137b48d95a346c37f59a7b9588 Binary files /dev/null and b/src/org/controlsfx/control/collapse.png differ diff --git a/src/org/controlsfx/control/decoration/Decoration.java b/src/org/controlsfx/control/decoration/Decoration.java new file mode 100644 index 0000000000000000000000000000000000000000..d5ff9867e7f94e48456ee7f0f1f2f6a6e38dc374 --- /dev/null +++ b/src/org/controlsfx/control/decoration/Decoration.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.decoration; + +import java.util.HashMap; +import java.util.Map; + +import javafx.scene.Node; + +/** + * Decoration is an abstract class used by the ControlsFX {@link Decorator} class + * for adding and removing decorations on a node. ControlsFX + * ships with pre-built decorations, including {@link GraphicDecoration} and + * {@link StyleClassDecoration}. + * + * <p>To better understand how to use the ControlsFX decoration API in your + * application, refer to the code samples and explanations in {@link Decorator}. + * + * @see Decorator + * @see GraphicDecoration + * @see StyleClassDecoration + */ +public abstract class Decoration { + + private volatile Map<String,Object> properties; + + /** + * Instantiates a default Decoration instance (obviously only callable by + * subclasses). + */ + protected Decoration() { + // no-op + } + + /** + * This method decorates the given + * target node with the relevant decorations, returning any 'decoration node' + * that needs to be added to the scenegraph (although this can be null). When + * the returned Node is null, this indicates that the decoration will be + * handled internally by the decoration (which is preferred, as the default + * implementation is not ideal in most circumstances). + * + * <p>When the boolean parameter is false, this method removes the decoration + * from the given target node, always returning null. + * + * @param targetNode The node to decorate. + * @return The decoration, but null is a valid return value. + */ + public abstract Node applyDecoration(Node targetNode); + + /** + * This method removes the decoration from the given target node. + * + * @param targetNode The node to undecorate. + */ + public abstract void removeDecoration(Node targetNode); + + /** + * Custom decoration properties + * @return decoration properties + */ + public synchronized final Map<String,Object> getProperties() { + if (properties == null) { + properties = new HashMap<>(); + } + return properties; + } +} diff --git a/src/org/controlsfx/control/decoration/Decorator.java b/src/org/controlsfx/control/decoration/Decorator.java new file mode 100644 index 0000000000000000000000000000000000000000..07435938ce43aeb55542a0752b4f507bb0b4ad91 --- /dev/null +++ b/src/org/controlsfx/control/decoration/Decorator.java @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.decoration; + +import impl.org.controlsfx.ImplUtils; +import impl.org.controlsfx.skin.DecorationPane; + +import java.util.*; +import java.util.function.Consumer; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.TextField; + +/** + * The Decorator class is responsible for accessing decorations for a given node. + * Through this class you may therefore add and remove decorations as desired. + * + * <h3>Code Example</h3> + * <p>Say you have a {@link TextField} that you want to decorate. You would simply + * do the following: + * + * <pre> + * {@code + * TextField textfield = new TextField(); + * Node decoration = ... // could be an ImageView or any Node! + * Decorator.addDecoration(textfield, new GraphicDecoration(decoration, Pos.CENTER_RIGHT));} + * </pre> + * + * <p>Similarly, if we wanted to add a CSS style class (e.g. because we have some + * css that knows to make the 'warning' style class turn the TextField a lovely + * shade of bright red, we would simply do the following: + * + * <pre> + * {@code + * TextField textfield = new TextField(); + * Decorator.addDecoration(textfield, new StyleClassDecoration("warning");} + * </pre> + * + * @see Decoration + */ +public class Decorator { + + + /*************************************************************************** + * * + * Static fields * + * * + **************************************************************************/ + + private final static String DECORATIONS_PROPERTY_KEY = "$org.controlsfx.decorations$"; //$NON-NLS-1$ + + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + private Decorator() { + // no op + } + + + + /*************************************************************************** + * * + * Static API * + * * + **************************************************************************/ + + /** + * Adds the given decoration to the given node. + * @param target The node to add the decoration to. + * @param decoration The decoration to add to the node. + */ + public static final void addDecoration(Node target, Decoration decoration) { + getDecorations(target, true).add(decoration); + updateDecorationsOnNode(target, FXCollections.observableArrayList(decoration), null); + } + + /** + * Removes the given decoration from the given node. + * @param target The node to remove the decoration from. + * @param decoration The decoration to remove from the node. + */ + public static final void removeDecoration(Node target, Decoration decoration) { + getDecorations(target, true).remove(decoration); + updateDecorationsOnNode(target, null, FXCollections.observableArrayList(decoration)); + } + + /** + * Removes all the decorations that have previously been set on the given node. + * @param target The node from which all previously set decorations should be removed. + */ + public static final void removeAllDecorations(Node target) { + List<Decoration> decorations = getDecorations(target, true); + List<Decoration> removed = FXCollections.observableArrayList(decorations); + + target.getProperties().remove(DECORATIONS_PROPERTY_KEY); + + updateDecorationsOnNode(target, null, removed); + } + + /** + * Returns all the currently set decorations for the given node. + * @param target The node for which all currently set decorations are required. + * @return An ObservableList of the currently set decorations for the given node. + */ + public static final ObservableList<Decoration> getDecorations(Node target) { + return getDecorations(target, false); + } + + + + /*************************************************************************** + * * + * Implementation * + * * + **************************************************************************/ + + private static final ObservableList<Decoration> getDecorations(Node target, boolean createIfAbsent) { + @SuppressWarnings("unchecked") + ObservableList<Decoration> decorations = (ObservableList<Decoration>) target.getProperties().get(DECORATIONS_PROPERTY_KEY); + if (decorations == null && createIfAbsent) { + decorations = FXCollections.observableArrayList(); + target.getProperties().put(DECORATIONS_PROPERTY_KEY, decorations); + } + return decorations; + } + + private static void updateDecorationsOnNode(Node target, List<Decoration> added, List<Decoration> removed) { + // find a DecorationPane parent and notify it that a node has updated + // decorations + getDecorationPane(target, (pane) -> pane.updateDecorationsOnNode(target, added, removed)); + } + + private static List<Scene> currentlyInstallingScenes = new ArrayList<>(); + private static Map<Scene, List<Consumer<DecorationPane>>> pendingTasksByScene = new HashMap<>(); + + private static void getDecorationPane(Node target, Consumer<DecorationPane> task) { + // find a DecorationPane parent and notify it that a node has updated + // decorations. If a DecorationPane doesn't exist, we install it into + // the scene. If a Scene does not exist, we add a listener to try again + // when a scene is available. + + DecorationPane pane = getDecorationPaneInParentHierarchy(target); + + if (pane != null) { + task.accept(pane); + } else { + // install decoration pane + final Consumer<Scene> sceneConsumer = scene -> { + if (currentlyInstallingScenes.contains(scene)) { + List<Consumer<DecorationPane>> pendingTasks = pendingTasksByScene.get(scene); + if (pendingTasks == null) { + pendingTasks = new LinkedList<>(); + pendingTasksByScene.put(scene, pendingTasks); + } + pendingTasks.add(task); + return; + } + + DecorationPane _pane = getDecorationPaneInParentHierarchy(target); + if (_pane == null) { + currentlyInstallingScenes.add(scene); + _pane = new DecorationPane(); + Node oldRoot = scene.getRoot(); + ImplUtils.injectAsRootPane(scene, _pane, true); + _pane.setRoot(oldRoot); + currentlyInstallingScenes.remove(scene); + } + + task.accept(_pane); + final List<Consumer<DecorationPane>> pendingTasks = pendingTasksByScene.remove(scene); + if (pendingTasks != null) { + for (Consumer<DecorationPane> pendingTask : pendingTasks) { + pendingTask.accept(_pane); + } + } + }; + + Scene scene = target.getScene(); + if (scene != null) { + sceneConsumer.accept(scene); + } else { + // install listener to try again later + InvalidationListener sceneListener = new InvalidationListener() { + @Override public void invalidated(Observable o) { + if (target.getScene() != null) { + target.sceneProperty().removeListener(this); + sceneConsumer.accept(target.getScene()); + } + } + }; + target.sceneProperty().addListener(sceneListener); + } + } + } + + private static DecorationPane getDecorationPaneInParentHierarchy(Node target) { + Parent p = target.getParent(); + while (p != null) { + if (p instanceof DecorationPane) { + return (DecorationPane) p; + } + p = p.getParent(); + } + return null; + } +} diff --git a/src/org/controlsfx/control/decoration/GraphicDecoration.java b/src/org/controlsfx/control/decoration/GraphicDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..ca521adc4d5bce3204286e6ab88b3a52235f2f9d --- /dev/null +++ b/src/org/controlsfx/control/decoration/GraphicDecoration.java @@ -0,0 +1,193 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.decoration; + +import impl.org.controlsfx.ImplUtils; + +import java.util.List; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; + +import javafx.geometry.Bounds; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.image.ImageView; + +/** + * GraphicDecoration is a {@link Decoration} designed to show a graphic (be it + * an image loaded via an {@link ImageView} or an arbitrarily complex + * scenegraph in its own right) on top of a given node. GraphicDecoration is + * applied as part of the ControlsFX {@link Decorator} API - refer to the + * {@link Decorator} javadoc for more details. + * + * @see Decoration + * @see Decorator + */ +public class GraphicDecoration extends Decoration { + + private final Node decorationNode; + private final Pos pos; + private final double xOffset; + private final double yOffset; + + /** + * Constructs a new GraphicDecoration with the given decoration node to be + * applied to any node that has this decoration applied to it. By default + * the decoration node will be applied in the top-left corner of the node. + * + * @param decorationNode The decoration node to apply to any node that has this + * decoration applied to it + */ + public GraphicDecoration(Node decorationNode) { + this(decorationNode, Pos.TOP_LEFT); + } + + /** + * Constructs a new GraphicDecoration with the given decoration node to be + * applied to any node that has this decoration applied to it, in the location + * provided by the {@link Pos position} argument. + * + * @param decorationNode The decoration node to apply to any node that has this + * decoration applied to it + * @param position The location to position the decoration node relative to the + * node that is being decorated. + */ + public GraphicDecoration(Node decorationNode, Pos position) { + this(decorationNode, position, 0, 0); + } + + /** + * Constructs a new GraphicDecoration with the given decoration node to be + * applied to any node that has this decoration applied to it, in the location + * provided by the {@link Pos position} argument, with the given xOffset and + * yOffset values used to adjust the position. + * + * @param decorationNode The decoration node to apply to any node that has this + * decoration applied to it + * @param position The location to position the decoration node relative to the + * node that is being decorated. + * @param xOffset The amount of movement to apply to the decoration node in the + * x direction (i.e. left and right). + * @param yOffset The amount of movement to apply to the decoration node in the + * y direction (i.e. up and down). + */ + public GraphicDecoration(Node decorationNode, Pos position, double xOffset, double yOffset) { + this.decorationNode = decorationNode; + this.decorationNode.setManaged(false); + this.pos = position; + this.xOffset = xOffset; + this.yOffset = yOffset; + } + + /** {@inheritDoc} */ + @Override public Node applyDecoration(Node targetNode) { + List<Node> targetNodeChildren = ImplUtils.getChildren((Parent)targetNode, true); + updateGraphicPosition(targetNode); + if (!targetNodeChildren.contains(decorationNode)) { + targetNodeChildren.add(decorationNode); + } + return null; + } + + /** {@inheritDoc} */ + @Override public void removeDecoration(Node targetNode) { + List<Node> targetNodeChildren = ImplUtils.getChildren((Parent)targetNode, true); + + if (targetNodeChildren.contains(decorationNode)) { + targetNodeChildren.remove(decorationNode); + } + } + + private void updateGraphicPosition(Node targetNode) { + final double decorationNodeWidth = decorationNode.prefWidth(-1); + final double decorationNodeHeight = decorationNode.prefHeight(-1); + + Bounds targetBounds = targetNode.getLayoutBounds(); + double x = targetBounds.getMinX(); + double y = targetBounds.getMinY(); + + double targetWidth = targetBounds.getWidth(); + if (targetWidth <= 0) { + targetWidth = targetNode.prefWidth(-1); + } + + double targetHeight = targetBounds.getHeight(); + if (targetHeight <= 0) { + targetHeight = targetNode.prefHeight(-1); + } + + /** + * If both targetWidth and targetHeight are equal to 0, this means the + * targetNode has not been laid out so we can put a listener in order to + * catch when the layout will be updated, and then we will place our + * decorationNode to the proper position. + */ + if (targetWidth <= 0 && targetHeight <= 0) { + targetNode.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() { + + @Override + public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) { + targetNode.layoutBoundsProperty().removeListener(this); + updateGraphicPosition(targetNode); + } + }); + } + + // x + switch (pos.getHpos()) { + case CENTER: + x += targetWidth/2 - decorationNodeWidth / 2.0; + break; + case LEFT: + x -= decorationNodeWidth / 2.0; + break; + case RIGHT: + x += targetWidth - decorationNodeWidth / 2.0; + break; + } + + // y + switch (pos.getVpos()) { + case CENTER: + y += targetHeight/2 - decorationNodeHeight / 2.0; + break; + case TOP: + y -= decorationNodeHeight / 2.0; + break; + case BOTTOM: + y += targetHeight - decorationNodeWidth / 2.0; + break; + case BASELINE: + y += targetNode.getBaselineOffset() - decorationNode.getBaselineOffset() - decorationNodeHeight / 2.0; + break; + } + + decorationNode.setLayoutX(x + xOffset); + decorationNode.setLayoutY(y + yOffset); + } +} diff --git a/src/org/controlsfx/control/decoration/StyleClassDecoration.java b/src/org/controlsfx/control/decoration/StyleClassDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..5e86b8b4659309028caa74ae57e3cd34d63b7d38 --- /dev/null +++ b/src/org/controlsfx/control/decoration/StyleClassDecoration.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.decoration; + +import java.util.List; + +import javafx.scene.Node; + +/** + * StyleClassDecoration is a {@link Decoration} designed to add a CSS style class + * to a node (for example, to show a warning style when the field is incorrectly + * set). StyleClassDecoration is applied as part of the ControlsFX {@link Decorator} + * API - refer to the {@link Decorator} javadoc for more details. + * + * @see Decoration + * @see Decorator + */ +public class StyleClassDecoration extends Decoration { + + private final String[] styleClasses; + + /** + * Constructs a new StyleClassDecoration with the given var-args array of + * style classes set to be applied to any node that has this decoration + * applied to it. + * + * @param styleClass A var-args array of style classes to apply to any node. + * @throws IllegalArgumentException if the styleClass varargs array is null or empty. + */ + public StyleClassDecoration(String... styleClass) { + if (styleClass == null || styleClass.length == 0) { + throw new IllegalArgumentException("var-arg style class array must not be null or empty"); //$NON-NLS-1$ + } + this.styleClasses = styleClass; + } + + /** {@inheritDoc} */ + @Override public Node applyDecoration(Node targetNode) { + final List<String> styleClassList = targetNode.getStyleClass(); + + for (String styleClass : styleClasses) { + if (styleClassList.contains(styleClass)) { + continue; + } + + styleClassList.add(styleClass); + } + + // no decoration node, so return null + return null; + } + + /** {@inheritDoc} */ + @Override public void removeDecoration(Node targetNode) { + targetNode.getStyleClass().removeAll(styleClasses); + } +} diff --git a/src/org/controlsfx/control/decoration/package-info.java b/src/org/controlsfx/control/decoration/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..da9c05887d71bc64c50b55bbe0ade63785097215 --- /dev/null +++ b/src/org/controlsfx/control/decoration/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing decoration-related API (that is, API to allow for users + * to 'decorate' nodes with additional nodes or css decorations. + */ +package org.controlsfx.control.decoration; \ No newline at end of file diff --git a/src/org/controlsfx/control/expand.png b/src/org/controlsfx/control/expand.png new file mode 100644 index 0000000000000000000000000000000000000000..c45d4ac09eaa74b5ab9894fa5eef2f28e9c46376 Binary files /dev/null and b/src/org/controlsfx/control/expand.png differ diff --git a/src/org/controlsfx/control/format-indent-more.png b/src/org/controlsfx/control/format-indent-more.png new file mode 100644 index 0000000000000000000000000000000000000000..d57376f6bbfd8bda976f998f1436a5bf2a801578 Binary files /dev/null and b/src/org/controlsfx/control/format-indent-more.png differ diff --git a/src/org/controlsfx/control/format-line-spacing-triple.png b/src/org/controlsfx/control/format-line-spacing-triple.png new file mode 100644 index 0000000000000000000000000000000000000000..8e80972978fabf98774a16acc6856b65137114c0 Binary files /dev/null and b/src/org/controlsfx/control/format-line-spacing-triple.png differ diff --git a/src/org/controlsfx/control/gridview.css b/src/org/controlsfx/control/gridview.css new file mode 100644 index 0000000000000000000000000000000000000000..ea2c3ca12072d09e02e1fae58e2b1025150c2952 --- /dev/null +++ b/src/org/controlsfx/control/gridview.css @@ -0,0 +1,7 @@ +.grid-view { + -fx-vertical-cell-spacing: 12; + -fx-horizontal-cell-spacing: 12; + -fx-cell-height: 64; + -fx-cell-width: 64; + -fx-horizontal-alignment: CENTER; +} diff --git a/src/org/controlsfx/control/info-overlay.css b/src/org/controlsfx/control/info-overlay.css new file mode 100644 index 0000000000000000000000000000000000000000..a18ded78e0c7eed831803f6757906ece954bcf89 --- /dev/null +++ b/src/org/controlsfx/control/info-overlay.css @@ -0,0 +1,15 @@ +.info-overlay > .info-panel { + -fx-background-color: lightgray; + -fx-opacity: .75; + -fx-padding: 5 5 5 5; +} + +.info-panel > .info-panel > .expand-button { + -fx-padding: 0 0 0 0 ; + /*-fx-graphic: url("/helloworld/david/collapse.png");*/ +} + +.info-panel > .info-panel > .collapse-button { + -fx-padding: 0 0 0 0 ; + /*-fx-graphic: url("expand.png");*/ +} diff --git a/src/org/controlsfx/control/listselectionview.css b/src/org/controlsfx/control/listselectionview.css new file mode 100644 index 0000000000000000000000000000000000000000..c13cd77f2ffb09c8a1da6c22d85cdadc7526c702 --- /dev/null +++ b/src/org/controlsfx/control/listselectionview.css @@ -0,0 +1,13 @@ +.list-selection-view { + -fx-padding: 10px; +} + +.list-selection-view > .grid-pane { + -fx-vgap: 10px; + -fx-hgap: 10px; +} + +.list-selection-view > .grid-pane > .list-header-label { + -fx-font-size: 1.0em; + -fx-font-weight: bold; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/maskerpane.css b/src/org/controlsfx/control/maskerpane.css new file mode 100644 index 0000000000000000000000000000000000000000..cf0c78da9b8ad89046aa7e2dba6145d90550fc20 --- /dev/null +++ b/src/org/controlsfx/control/maskerpane.css @@ -0,0 +1,17 @@ +.masker-pane {} + +.masker-pane .masker-glass { + -fx-background-color: rgba(0, 0, 0, .3); +} + +.masker-pane .masker-text { + -fx-text-fill: white; + -fx-font-weight: bold; +} + +.masker-pane .masker-center { + -fx-background-color: rgba(0, 0, 0, .6); + -fx-max-height: 1.50in; + -fx-padding: .25in; + -fx-background-radius: .1in; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/masterdetailpane.css b/src/org/controlsfx/control/masterdetailpane.css new file mode 100644 index 0000000000000000000000000000000000000000..82d11f1998525f6072d119a1fceea3906dd683ca --- /dev/null +++ b/src/org/controlsfx/control/masterdetailpane.css @@ -0,0 +1,14 @@ +.tab-pane > * > .master-detail-pane > .split-pane, +.split-pane > * > .master-detail-pane > .split-pane { + -fx-background-insets: 0, 0; + -fx-padding: 0; + } +.tab-pane.floating > * > .master-detail-pane > .split-pane { + -fx-background-insets: 0, 0; + -fx-padding: -1; +} +.titled-pane > * > * > .master-detail-pane > .split-pane { + -fx-background-color: null; + -fx-background-insets: 0, 0; + -fx-padding: 0; +} diff --git a/src/org/controlsfx/control/notificationpane.css b/src/org/controlsfx/control/notificationpane.css new file mode 100644 index 0000000000000000000000000000000000000000..765ec1b7614eaec42311a8afea1cb5e9092ec328 --- /dev/null +++ b/src/org/controlsfx/control/notificationpane.css @@ -0,0 +1,103 @@ +/****************************************************************************** + * + * Light theme + * + *****************************************************************************/ + +.notification-pane .notification-bar > .pane { + -fx-background-color: -fx-outer-border, -fx-inner-border, -fx-body-color; + -fx-padding: 0 7 0 7; +} + +.notification-pane.top .notification-bar > .pane { + -fx-background-insets: 0 0 0 0, 0 0 1 0, 0 0 2 0; +} + +.notification-pane.bottom .notification-bar > .pane { + -fx-background-insets: 0 0 0 0, 1 0 0 0, 2 0 0 0; +} + +.notification-pane .notification-bar > .pane .label { + -fx-font-size: 1.166667em; /*15px;*/ + -fx-text-fill: #292929; +} + + +/****************************************************************************** + * + * Dark theme + * + *****************************************************************************/ + +.notification-pane.dark .notification-bar > .pane { + -fx-background-color: linear-gradient(#595959, #474747 37%, #343434); +} + +.notification-pane.dark .notification-bar > .pane .label { + -fx-text-fill: #ebebeb; +} + + +/****************************************************************************** + * + * Drop shadow support + * + *****************************************************************************/ + +/* NotificationPane shows from the top, so put shadow at bottom of bar */ +.notification-pane.top .notification-bar > .pane { + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.4), 11, 0.0, 0, 3); +} + +/* + * We could have a drop shadow at the top of the bar when it appears from the + * bottom, but it doesn't look right as the gradient is running in the opposite + * direction of the drop shadow. Therefore, the following is commented out, + * but it can always be re-enabled in the future if desired. + */ + /* +.notification-pane:bottom .notification-bar > .pane { + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.4), 11, 0.0, 0, -3); +} +*/ + + + +/****************************************************************************** + * + * Close button + * + *****************************************************************************/ +.notification-pane .notification-bar > .pane .close-button { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 2; + -fx-padding: 0 0 0 0; + -fx-alignment: center; +} + +.notification-pane .notification-bar > .pane .close-button:hover { + -fx-background-color: linear-gradient(#a3a3a3, #8b8b8b 34%, #777777 36%, #777777 63%, #8b8b8b 65%, #adadad); +} + +.notification-pane .notification-bar > .pane .close-button:pressed { + -fx-background-color: linear-gradient(#a3a3a3, #8b8b8b 34%, #777777 36%, #777777 63%, #8b8b8b 65%, #adadad); +} + +.notification-pane .notification-bar > .pane .close-button > .graphic { + -fx-background-color: #949494; + -fx-scale-shape: false; + -fx-padding: 4.5 4.5 4.5 4.5; /* Graphic is 9x9 px */ +} + +.notification-pane .notification-bar > .pane .close-button:hover > .graphic { + -fx-background-color: #fefeff; +} + +.notification-pane .notification-bar > .pane .close-button:pressed > .graphic { + -fx-background-color: #cfcfcf; +} + +.notification-pane .notification-bar > .pane .close-button > .graphic { + -fx-shape: "M395.992,296.758l1.794-1.794l7.292,7.292l-1.795,1.794 L395.992,296.758z M403.256,294.992l1.794,1.794l-7.292,7.292l-1.794-1.795 L403.256,294.992z"; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/notificationpopup.css b/src/org/controlsfx/control/notificationpopup.css new file mode 100644 index 0000000000000000000000000000000000000000..a35fa1575d11122e7fc8ff11bd8a9f524efb727e --- /dev/null +++ b/src/org/controlsfx/control/notificationpopup.css @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Light theme + * + *****************************************************************************/ + +.notification-bar > .pane { + -fx-background-color: -fx-outer-border, -fx-inner-border, -fx-body-color; + -fx-padding: 7 7 7 7; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.4), 11, 0.0, 0, 3); + -fx-background-insets: 0, 1, 2; +} + +.notification-bar > .pane .title { + -fx-font-size: 1.166667em; /*15px;*/ + -fx-text-fill: #292929; + -fx-font-weight: bold; +} + +.notification-bar > .pane .label { + -fx-font-size: 1.166667em; /*15px;*/ + -fx-text-fill: #292929; + -fx-alignment: top-left; +} + +/****************************************************************************** + * + * Dark theme + * + *****************************************************************************/ + +.notification-bar.dark > .pane { + -fx-background-color: -fx-outer-border, -fx-inner-border, linear-gradient(#595959, #474747 37%, #343434); +} + +.notification-bar.dark > .pane .title, +.notification-bar.dark > .pane .label { + -fx-text-fill: #ebebeb; +} + + +/****************************************************************************** + * + * Close button + * + *****************************************************************************/ + .notification-bar > .pane .close-button { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 2; + -fx-padding: 0 0 0 0; + -fx-alignment: center; +} + +.notification-bar > .pane .close-button:hover { + -fx-background-color: linear-gradient(#a3a3a3, #8b8b8b 34%, #777777 36%, #777777 63%, #8b8b8b 65%, #adadad); +} + +.notification-bar > .pane .close-button:pressed { + -fx-background-color: linear-gradient(#a3a3a3, #8b8b8b 34%, #777777 36%, #777777 63%, #8b8b8b 65%, #adadad); +} + +.notification-bar > .pane .close-button > .graphic { + -fx-background-color: #949494; + -fx-scale-shape: false; + -fx-padding: 4.5 4.5 4.5 4.5; /* Graphic is 9x9 px */ +} + +.notification-bar > .pane .close-button:hover > .graphic { + -fx-background-color: #fefeff; +} + +.notification-bar > .pane .close-button:pressed > .graphic { + -fx-background-color: #cfcfcf; +} + +.notification-bar > .pane .close-button > .graphic { + -fx-shape: "M395.992,296.758l1.794-1.794l7.292,7.292l-1.795,1.794 L395.992,296.758z M403.256,294.992l1.794,1.794l-7.292,7.292l-1.794-1.795 L403.256,294.992z"; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/open-editor.png b/src/org/controlsfx/control/open-editor.png new file mode 100644 index 0000000000000000000000000000000000000000..87d22c3c98b41d3264f0b8fc48f72517e2b184ec Binary files /dev/null and b/src/org/controlsfx/control/open-editor.png differ diff --git a/src/org/controlsfx/control/package-info.java b/src/org/controlsfx/control/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f22fac2bba00a6bb314da253454f0aa51e5473c6 --- /dev/null +++ b/src/org/controlsfx/control/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing a number of useful controls-related classes that do not + * exist in the base JavaFX distribution. + */ +package org.controlsfx.control; \ No newline at end of file diff --git a/src/org/controlsfx/control/plusminusslider.css b/src/org/controlsfx/control/plusminusslider.css new file mode 100644 index 0000000000000000000000000000000000000000..86cb6f1b74c9d71aa65216b864add559ce84fed8 --- /dev/null +++ b/src/org/controlsfx/control/plusminusslider.css @@ -0,0 +1,57 @@ +.plus-minus-slider { + -fx-background-radius: 2.0; + -fx-border-color: -fx-box-border; + -fx-border-radius: 2.0; +} + +.plus-minus-slider:horizontal { + -fx-background-color: derive(-fx-box-border,30.0%), linear-gradient(to bottom, derive(-fx-base,-3.0%), derive(-fx-base,5.0%) 50.0%, derive(-fx-base,-3.0%)); + -fx-background-insets: 0.0, 1.0 0.0 1.0 0.0; + -fx-pref-height: 16.0; + -fx-max-height: 16.0; +} + +.plus-minus-slider:vertical { + -fx-background-color: derive(-fx-box-border,30.0%), linear-gradient(to right, derive(-fx-base,-3.0%), derive(-fx-base,5.0%) 50.0%, derive(-fx-base,-3.0%)); + -fx-background-insets: 0.0, 0.0 1.0 0.0 1.0; + -fx-pref-width: 16.0; + -fx-max-width: 16.0; +} + +.plus-minus-slider > * > .slider { + -fx-show-tick-marks: false; +} + +.plus-minus-slider > * > .slider > .track { + -fx-background-color: transparent; +} + +.plus-minus-slider > * > .slider > .thumb { + -fx-background-color: -fx-outer-border, -fx-inner-border, -fx-body-color; + -fx-background-insets: 2.0, 3.0, 4.0; + -fx-background-radius: 3.0, 2.0, 1.0; +} + +.plus-minus-slider > * > .slider:focused > .thumb { + -fx-background-color: -fx-focus-color, -fx-inner-border, -fx-body-color, -fx-faint-focus-color, -fx-body-color; + -fx-background-insets: 1.8, 3.0, 4.0, 0.6, 4.6; + -fx-background-radius: 3.0, 2.0, 1.0, 4.0, 1.0; +} + +.plus-minus-slider > * > .adjust-plus { + -fx-pref-width: 16.0; + -fx-pref-height: 16.0; + -fx-shape: "M0,3 H3 V0 H5 V3 H8 V5 H5 V8 H3 V5 H0 V3 Z"; + -fx-scale-shape: false; + -fx-effect: dropshadow(two-pass-box , -fx-shadow-highlight-color, 1.0, 0.0 , 0.0, 1.4); + -fx-background-color: -fx-mark-highlight-color,derive(-fx-base,-45.0%); +} + +.plus-minus-slider > * > .adjust-minus { + -fx-pref-width: 16.0; + -fx-pref-height: 16.0; + -fx-shape: "M0,0H8V2H0Z"; + -fx-scale-shape: false; + -fx-effect: dropshadow(two-pass-box , -fx-shadow-highlight-color, 1.0, 0.0 , 0.0, 1.4); + -fx-background-color: -fx-mark-highlight-color,derive(-fx-base,-45.0%); +} diff --git a/src/org/controlsfx/control/popover.css b/src/org/controlsfx/control/popover.css new file mode 100644 index 0000000000000000000000000000000000000000..c9dd527cd66d336340e15e5c9b1b6defc44c0caa --- /dev/null +++ b/src/org/controlsfx/control/popover.css @@ -0,0 +1,36 @@ +.popover { + -fx-background-color: transparent; +} + +.popover > .border { + -fx-stroke: linear-gradient(to bottom, rgba(0,0,0, .3), rgba(0, 0, 0, .7)) ; + -fx-stroke-width: 0.5; + -fx-fill: rgba(255.0,255.0,255.0, .95); + -fx-effect: dropshadow(gaussian, rgba(0,0,0,.2), 10.0, 0.5, 2.0, 2.0); +} + +.popover > .content { +} + +.popover > .detached { +} + +.popover > .content > .title > .text { + -fx-padding: 6.0 6.0 0.0 6.0; + -fx-text-fill: rgba(120, 120, 120, .8); + -fx-font-weight: bold; +} + +.popover > .content > .title > .icon { + -fx-padding: 6.0 0.0 0.0 10.0; +} + +.popover > .content > .title > .icon > .graphics > .circle { + -fx-fill: gray ; + -fx-effect: innershadow(gaussian, rgba(0,0,0,.2), 3, 0.5, 1.0, 1.0); +} + +.popover > .content > .title > .icon > .graphics > .line { + -fx-stroke: white ; + -fx-stroke-width: 2; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/propertysheet.css b/src/org/controlsfx/control/propertysheet.css new file mode 100644 index 0000000000000000000000000000000000000000..3846aca5d56cdae5954330a984d485e331a6aa81 --- /dev/null +++ b/src/org/controlsfx/control/propertysheet.css @@ -0,0 +1,15 @@ +/* Remove extraneous borders from PropertySheet */ + +.property-sheet .scroll-pane .property-pane { + -fx-background-color: -fx-background; +} + +.property-sheet .scroll-pane { + -fx-background-color: -fx-background; + -fx-background-insets: 0; + -fx-padding: 0; +} + +.property-sheet .scroll-pane .accordion { + -fx-padding: -1; +} diff --git a/src/org/controlsfx/control/rangeslider.css b/src/org/controlsfx/control/rangeslider.css new file mode 100644 index 0000000000000000000000000000000000000000..373e9d5c3fa095845318b6399ed3ff3521828521 --- /dev/null +++ b/src/org/controlsfx/control/rangeslider.css @@ -0,0 +1,86 @@ + +/******************************************************************************* + * * + * RangeSlider * + * (Largely derived from Modena styles) * + * * + ******************************************************************************/ + +.range-slider .low-thumb, +.range-slider .high-thumb { + -fx-background-color: + linear-gradient(to bottom, derive(-fx-text-box-border, -20%), derive(-fx-text-box-border, -30%)), + -fx-inner-border, + -fx-body-color; + -fx-background-insets: 0, 1, 2; + -fx-background-radius: 1.0em; /* makes sure this remains circular */ + -fx-padding: 0.583333em; /* 7 */ + -fx-effect: dropshadow(two-pass-box , rgba(0, 0, 0, 0.1), 5, 0.0 , 0, 2); +} + +.range-slider:focused .low-thumb, +.range-slider:focused .high-thumb { + -fx-background-radius: 1.0em; /* makes sure this remains circular */ +} + +.range-slider .low-thumb:focused, +.range-slider .high-thumb:focused { + -fx-background-color: + -fx-focus-color, + derive(-fx-color,-36%), + derive(-fx-color,73%), + linear-gradient(to bottom, derive(-fx-color,-19%),derive(-fx-color,61%)); + -fx-background-insets: -1.4, 0, 1, 2; + -fx-background-radius: 1.0em; /* makes sure this remains circular */ +} + +.range-slider .low-thumb:hover, +.range-slider .high-thumb:hover { + -fx-color: -fx-hover-base; +} + +.range-slider .range-bar { + -fx-background-color: -fx-focus-color; +} + +.range-slider .low-thumb:pressed, +.range-slider .high-thumb:pressed { + -fx-color: -fx-pressed-base; +} + +.range-slider .track { + -fx-background-color: + -fx-shadow-highlight-color, + linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border), + linear-gradient(to bottom, + derive(-fx-control-inner-background, -9%), + derive(-fx-control-inner-background, 0%), + derive(-fx-control-inner-background, -5%), + derive(-fx-control-inner-background, -12%) + ); + -fx-background-insets: 0 0 -1 0, 0, 1; + -fx-background-radius: 0.25em, 0.25em, 0.166667em; /* 3 3 2 */ + -fx-padding: 0.25em; /* 3 */ +} + +.range-slider:vertical .track { + -fx-background-color: + -fx-shadow-highlight-color, + -fx-text-box-border, + linear-gradient(to right, + derive(-fx-control-inner-background, -9%), + -fx-control-inner-background, + derive(-fx-control-inner-background, -9%) + ); +} + +.range-slider .axis { + -fx-tick-label-fill: derive(-fx-text-background-color, 30%); + -fx-tick-length: 5px; + -fx-minor-tick-length: 3px; + -fx-border-color: null; +} + +.range-slider:disabled { + -fx-opacity: 0.4; +} diff --git a/src/org/controlsfx/control/rating.css b/src/org/controlsfx/control/rating.css new file mode 100644 index 0000000000000000000000000000000000000000..393379dbd33176d75a1abe6dd26de87283760eff --- /dev/null +++ b/src/org/controlsfx/control/rating.css @@ -0,0 +1,15 @@ +.rating > .container { + -fx-spacing: 4; +} +.rating > .container > .button { + -fx-background-color: transparent; + -fx-background-image: url("unselected-star.png"); + -fx-padding: 16 16; + -fx-background-image-repeat: no-repeat; +} +.rating > .container > .button.strong { + -fx-background-image: url("selected-star.png"); +} +.rating > .container > .button:hover { + -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 8, 0.0 , 0 , 0 ); +} \ No newline at end of file diff --git a/src/org/controlsfx/control/segmentedbutton.css b/src/org/controlsfx/control/segmentedbutton.css new file mode 100644 index 0000000000000000000000000000000000000000..0e90a1dfca673351ecc64ee76842c92186cd6c76 --- /dev/null +++ b/src/org/controlsfx/control/segmentedbutton.css @@ -0,0 +1,79 @@ +/* -------- Segmented Button ---------------- */ +.segmented-button.dark .toggle-button { + -fx-padding: 3 15 3 15; + -fx-border-color: transparent -fx-outer-border transparent transparent; +} + +.segmented-button.dark .toggle-button:focused { + -fx-background-color: + rgba(23,134,248,0.2), + -fx-focus-color, + -fx-inner-border, + -fx-body-color; +} + +.segmented-button.dark .toggle-button:selected Text { + -fx-effect: dropshadow( one-pass-box , rgba(0,0,0,0.9) , 2, 0.0 , 0 , 1 ); +} + +.segmented-button.dark .toggle-button:selected { + -fx-background-color: + -fx-shadow-highlight-color, + linear-gradient( to bottom, derive(-fx-color,-90%) 0%, derive(-fx-color,-60%) 100% ), + linear-gradient( to bottom, derive(-fx-color,-60%) 0%, derive(-fx-color,-35%) 50%, derive(-fx-color,-30%) 98%, derive(-fx-color,-50%) 100% ), + linear-gradient( to right, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0) 10%, rgba(0,0,0,0) 90%, rgba(0,0,0,0.3) 100% ); + -fx-background-insets: 0 0 -1 0, 0, 1, 1; + /* TODO: -fx-text-fill should be derived */ + -fx-text-fill: -fx-light-text-color; +} + +/* *************************** LEFT BUTTON ************************** */ +.segmented-button.dark .toggle-button.left-pill { + -fx-background-radius: 3 0 0 3; + -fx-background-insets: 0 0 -1 0, 0, 1 0 1 1, 2 0 2 2; +} + +.segmented-button.dark .toggle-button.left-pill:focused { + -fx-background-insets: -2 0 -2 -2, 0 0 0 0, 1, 2; + -fx-border-color: transparent; +} + +.segmented-button.dark .toggle-button.left-pill:selected:focused { + -fx-background-insets: 0 0 -1 0, 0, 1 0 1 1, 1 0 1 1; + -fx-border-color: transparent; +} + +/* *************************** RIGHT BUTTON ************************** */ +.segmented-button.dark .toggle-button.right-pill { + -fx-background-radius: 0 3 3 0; + -fx-background-insets: 0 0 -1 0, 0, 1 1 1 0, 2 2 2 0; + -fx-border-color: transparent; +} + +.segmented-button.dark .toggle-button.right-pill:focused { + -fx-background-insets: -2 -2 -2 0, 0, 1, 2; + -fx-border-color: transparent; +} + +.segmented-button.dark .toggle-button.right-pill:selected:focused { + -fx-background-insets: -1 -1 -1 -1, 0 0 0 -1, 1 1 1 0, 1 1 1 0; + -fx-border-color: transparent; +} + +/* *************************** CENTER BUTTON ************************** */ +.segmented-button.dark .toggle-button.center-pill { + -fx-background-radius: 0; + -fx-background-insets: 0 0 -1 0, 0, 1 0 1 0, 2 0 2 0; + +} + +.segmented-button.dark .toggle-button.center-pill:focused { + -fx-background-radius: 0; + -fx-background-insets: -2 0 -2 -2, 0 0 0 -1, 1 1 1 0, 2 2 2 1; + -fx-border-color: transparent; +} + +.segmented-button.dark .toggle-button.center-pill:selected:focused { + -fx-background-insets: -1.4 0 -1.4 -1, 0 0 0 -1, 1 1 1 0, 1 1 1 0; + -fx-border-color: transparent; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/selected-star.png b/src/org/controlsfx/control/selected-star.png new file mode 100644 index 0000000000000000000000000000000000000000..01b0fc0457913c694471b844d50ed84418790b24 Binary files /dev/null and b/src/org/controlsfx/control/selected-star.png differ diff --git a/src/org/controlsfx/control/snapshot-view.css b/src/org/controlsfx/control/snapshot-view.css new file mode 100644 index 0000000000000000000000000000000000000000..1aeaeeeef84f6147eff0b06ea45be24dc22970ad --- /dev/null +++ b/src/org/controlsfx/control/snapshot-view.css @@ -0,0 +1,4 @@ + +.snapshot-view { + +} \ No newline at end of file diff --git a/src/org/controlsfx/control/spreadsheet/Grid.java b/src/org/controlsfx/control/spreadsheet/Grid.java new file mode 100644 index 0000000000000000000000000000000000000000..53ac9e8ecdef75e57298b07d31ab58b0ee646943 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/Grid.java @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import java.util.Collection; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.event.EventType; +import org.controlsfx.control.spreadsheet.SpreadsheetView.SpanType; + +/** + * That class holds some {@link SpreadsheetCell} in order + * to be used by the {@link SpreadsheetView}. + * + * A Grid is used by {@link SpreadsheetView} to represent the data to show on + * screen. A default implementation is provided by {@link GridBase}, but for + * more custom purposes (e.g. loading data on demand), this Grid interface may + * prove useful. + * + * <p>A Grid at its essence consists of rows and columns. Critical to the + * SpreadsheetView is that the {@link #getRowCount() row count} and + * {@link #getColumnCount() column count} are accurately returned when requested + * (even if the data returned by {@link #getRows()} is not all fully loaded into + * memory). + * + * <p>Whilst the {@link #getRows()} return type may appear confusing, it is + * actually quite logical when you think about it: {@link #getRows()} returns an + * ObservableList of ObservableList of {@link SpreadsheetCell} instances. In other + * words, this is your classic 2D collection, where the outer ObservableList + * can be thought of as the rows, and the inner ObservableList as the columns + * within each row. Therefore, if you are wanting to iterate through all columns + * in every row of the grid, you would do something like this: + * + * + * <h3> Code Sample </h3> + * <pre> + * Grid grid = ... + * for (int row = 0; row < grid.getRowCount(); row++) { + * for (int column = 0; column < grid.getColumnCount(); column++) { + * SpreadsheetCell<?> cell = getRows().get(row).get(column); + * doStuff(cell); + * } + * } + * + * </pre> + * + * @see SpreadsheetView + * @see GridBase + * @see SpreadsheetCell + */ +public interface Grid { + /** + * This value may be returned from {@link #getRowHeight(int) } in order to + * let the system compute the best row height. + */ + public static final double AUTOFIT = -1; + + /** + * @return how many rows are inside the grid. + */ + public int getRowCount(); + + /** + * @return how many columns are inside the grid. + */ + public int getColumnCount(); + + /** + * Return an ObservableList of ObservableList of {@link SpreadsheetCell} + * instances. Refer to the {@link Grid} class javadoc for more detail. + * @return an ObservableList of ObservableList of {@link SpreadsheetCell} + * instances + */ + public ObservableList<ObservableList<SpreadsheetCell>> getRows(); + + /** + * Change the value situated at the intersection if possible. + * Verification and conversion of the value should be done before + * with {@link SpreadsheetCellType#match(Object)} + * and {@link SpreadsheetCellType#convertValue(Object)}. + * @param row + * @param column + * @param value + */ + public void setCellValue(int row, int column, Object value); + + /** + * Return the {@link SpanType} for a given cell row/column intersection. + * @param spv + * @param row + * @param column + * @return the {@link SpanType} for a given cell row/column intersection. + */ + public SpanType getSpanType(final SpreadsheetView spv, final int row, final int column); + + /** + * Return the height of a row. {@link #AUTOFIT } can be returned in order to + * let the system compute the best row height. + * + * @param row + * @return the height of a row. + */ + public double getRowHeight(int row); + + /** + * Return true if the specified row is resizable. + * @param row + * @return true if the specified row is resizable. + */ + public boolean isRowResizable(int row); + + /** + * Returns an ObservableList of string to display in the row headers. + * + * @return an ObservableList of string to display in the row headers. + */ + public ObservableList<String> getRowHeaders(); + + /** + * Returns an ObservableList of string to display in the column headers. + * + * @return an ObservableList of string to display in the column headers. + */ + public ObservableList<String> getColumnHeaders(); + + /** + * Span in row the cell situated at rowIndex and colIndex by the number + * count + * + * @param count + * @param rowIndex + * @param colIndex + */ + public void spanRow(int count, int rowIndex, int colIndex); + + /** + * Span in column the cell situated at rowIndex and colIndex by the number + * count + * + * @param count + * @param rowIndex + * @param colIndex + */ + public void spanColumn(int count, int rowIndex, int colIndex); + + /** + * This method sets the rows used by the grid, and updates the rowCount. + * @param rows + */ + public void setRows(Collection<ObservableList<SpreadsheetCell>> rows); + + /** + * Registers an event handler to this Grid. The Grid class allows + * registration of listeners which will be notified as a {@link SpreadsheetCell}'s value + * will change. + * + * @param <E> + * @param eventType the type of the events to receive by the handler + * @param eventHandler the handler to register + * @throws NullPointerException if the event type or handler is null + */ + public <E extends GridChange> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler); + + /** + * Unregisters a previously registered event handler from this Grid. One + * handler might have been registered for different event types, so the + * caller needs to specify the particular event type from which to + * unregister the handler. + * + * @param <E> + * @param eventType the event type from which to unregister + * @param eventHandler the handler to unregister + * @throws NullPointerException if the event type or handler is null + */ + public <E extends GridChange> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler); +} \ No newline at end of file diff --git a/src/org/controlsfx/control/spreadsheet/GridBase.java b/src/org/controlsfx/control/spreadsheet/GridBase.java new file mode 100644 index 0000000000000000000000000000000000000000..16183e27d480d2516bb4ff69e53583caa118b414 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/GridBase.java @@ -0,0 +1,414 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import com.sun.javafx.event.EventHandlerManager; +import impl.org.controlsfx.spreadsheet.GridViewSkin; +import java.util.BitSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.event.EventType; +import javafx.util.Callback; +import org.controlsfx.control.spreadsheet.SpreadsheetView.SpanType; + +/** + * A base implementation of the {@link Grid} interface. + * + * <h3>Row Height</h3> + * + * You can specify some row height for some of your rows at the beginning. + * You have to use the method {@link #setRowHeightCallback(javafx.util.Callback) } + * in order to specify a Callback that will give you the index of the row, and you + * will give back the height of the row. + * <br> + * If you just have a {@link Map} available, you can use the {@link MapBasedRowHeightFactory} + * that will construct the Callback for you. + +* The default height is 24.0. + * + * <h3>Cell values</h3> + * <p> + * If you want to change the value of a cell, you have to go through the API + * with {@link #setCellValue(int, int, Object)}. This method will verify that + * the value is corresponding to the {@link SpreadsheetCellType} of the cell and + * try to convert it if possible. It will also fire a {@link GridChange} event + * in order to notify all listeners that a value has changed. <br> + * <p> + * If you want to listen to those changes, you can use the + * {@link #addEventHandler(EventType, EventHandler)} and + * {@link #removeEventHandler(EventType, EventHandler)} methods. <br> + * A basic listener for implementing a undo/redo in the SpreadsheetView could be + * like that: + * + * <pre> + * Grid grid = ...; + * Stack<GridChange> undoStack = ...; + * grid.addEventHandler(GridChange.GRID_CHANGE_EVENT, new EventHandler<GridChange>() { + * + * public void handle(GridChange change) { + * undoStack.push(change); + * } + * }); + * + * </pre> + * + * + * @see Grid + * @see GridChange + */ +public class GridBase implements Grid, EventTarget { + + /*************************************************************************** + * + * Private Fields + * + **************************************************************************/ + private final ObservableList<ObservableList<SpreadsheetCell>> rows; + + private int rowCount; + private int columnCount; + private Callback<Integer, Double> rowHeightFactory; + private final BooleanProperty locked; + private final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); + private final ObservableList<String> rowsHeader; + private final ObservableList<String> columnsHeader; + private BitSet resizableRow; + + /*************************************************************************** + * + * Constructor + * + **************************************************************************/ + + /** + * Creates a grid with a fixed number of rows and columns. + * + * @param rowCount + * @param columnCount + */ + public GridBase(int rowCount, int columnCount) { + this.rowCount = rowCount; + this.columnCount = columnCount; + rowsHeader = FXCollections.observableArrayList(); + columnsHeader = FXCollections.observableArrayList(); + locked = new SimpleBooleanProperty(false); + rowHeightFactory = new MapBasedRowHeightFactory(new HashMap<>()); + rows = FXCollections.observableArrayList(); + rows.addListener((Observable observable) -> { + setRowCount(rows.size()); + }); + resizableRow = new BitSet(rowCount); + resizableRow.set(0, rowCount, true); + } + + /*************************************************************************** + * + * Public Methods (Inherited from Grid) + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override + public ObservableList<ObservableList<SpreadsheetCell>> getRows() { + return rows; + } + + /** {@inheritDoc} */ + @Override + public void setCellValue(int row, int column, Object value) { + if (row < rowCount && column < columnCount && !isLocked()) { + SpreadsheetCell cell = getRows().get(row).get(column); + Object previousItem = cell.getItem(); + Object convertedValue = cell.getCellType().convertValue(value); + cell.setItem(convertedValue); + if (!java.util.Objects.equals(previousItem, cell.getItem())) { + GridChange cellChange = new GridChange(row, column, previousItem, convertedValue); + Event.fireEvent(this, cellChange); + } + } + } + + /** {@inheritDoc} */ + @Override + public int getRowCount() { + return rowCount; + } + + /** {@inheritDoc} */ + @Override + public int getColumnCount() { + return columnCount; + } + + /** {@inheritDoc} */ + @Override + public SpanType getSpanType(final SpreadsheetView spv, final int row, final int column) { + if (row < 0 || column < 0 || row >= rowCount || column >= columnCount) { + return SpanType.NORMAL_CELL; + } + + final SpreadsheetCell cell = getRows().get(row).get(column); + + final int cellColumn = cell.getColumn(); + final int cellRow = cell.getRow(); + final int cellRowSpan = cell.getRowSpan(); + + if (cellColumn == column && cellRow == row && cellRowSpan == 1) { + return SpanType.NORMAL_CELL; + } + + final int cellColumnSpan = cell.getColumnSpan(); + /** + * This is a consuming operation so we place it after the normal_cell + * case since this is the most typical case. + */ + final GridViewSkin skin = spv.getCellsViewSkin(); + final boolean containsRowMinusOne = skin == null ? true : skin.containsRow(row - 1); + if (containsRowMinusOne && cellColumnSpan > 1 && cellColumn != column && cellRowSpan > 1 + && cellRow != row) { + return SpanType.BOTH_INVISIBLE; + } else if (cellRowSpan > 1 && cellColumn == column) { + if ((cellRow == row || !containsRowMinusOne)) { + return SpanType.ROW_VISIBLE; + } else { + return SpanType.ROW_SPAN_INVISIBLE; + } + } else if (cellColumnSpan > 1 && cellColumn != column && (cellRow == row || !containsRowMinusOne)) { + return SpanType.COLUMN_SPAN_INVISIBLE; + } else { + return SpanType.NORMAL_CELL; + } + } + + /** + * Return the height of a row. + * It will first look into the {@link Callback}provided at the + * initialization. If nothing's found, {@link #AUTOFIT} will be returned. + * @param row + * @return the height of a row. + */ + @Override + public double getRowHeight(int row) { + return rowHeightFactory.call((Integer) row); + } + + /*************************************************************************** + * + * Public Methods + * + **************************************************************************/ + + /** + * Set a new {@link Callback} for this grid in order to specify height of + * each row. + * + * @param rowHeight + */ + public void setRowHeightCallback(Callback<Integer, Double> rowHeight) { + this.rowHeightFactory = rowHeight; + } + + /** {@inheritDoc} */ + @Override + public ObservableList<String> getRowHeaders() { + return rowsHeader; + } + + /** {@inheritDoc} */ + @Override + public ObservableList<String> getColumnHeaders() { + return columnsHeader; + } + + /** + * Return a BooleanProperty associated with the locked grid state. It means + * that the Grid is in a read-only mode and that no SpreadsheetCell can be + * modified, no regards for their own + * {@link SpreadsheetCell#isEditable()} state. + * + * @return a BooleanProperty associated with the locked grid state. + */ + public BooleanProperty lockedProperty() { + return locked; + } + + /** + * Return whether this Grid id locked or not. + * + * @return whether this Grid id locked or not. + */ + public boolean isLocked() { + return locked.get(); + } + + /** + * Lock or unlock this Grid. + * + * @param lock + */ + public void setLocked(Boolean lock) { + locked.setValue(lock); + } + + /** {@inheritDoc} */ + @Override + public void spanRow(int count, int rowIndex, int colIndex) { + if (count <= 0 || count > rowCount || rowIndex >= rowCount || colIndex >= columnCount) { + return; + } + final SpreadsheetCell cell = rows.get(rowIndex).get(colIndex); + final int colSpan = cell.getColumnSpan(); + final int rowSpan = count; + cell.setRowSpan(rowSpan); + for (int row = rowIndex; row < rowIndex + rowSpan && row < rowCount; ++row) { + for (int col = colIndex; col < colIndex + colSpan && col < columnCount; ++col) { + if (row != rowIndex || col != colIndex) { + rows.get(row).set(col, cell); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public void spanColumn(int count, int rowIndex, int colIndex) { + if (count <= 0 || count > columnCount || rowIndex >= rowCount || colIndex >= columnCount) { + return; + } + final SpreadsheetCell cell = rows.get(rowIndex).get(colIndex); + final int colSpan = count; + final int rowSpan = cell.getRowSpan(); + cell.setColumnSpan(colSpan); + for (int row = rowIndex; row < rowIndex + rowSpan && row < rowCount; ++row) { + for (int col = colIndex; col < colIndex + colSpan && col < columnCount; ++col) { + if (row != rowIndex || col != colIndex) { + rows.get(row).set(col, cell); + } + } + } + } + + /** {@inheritDoc} */ + @Override + public void setRows(Collection<ObservableList<SpreadsheetCell>> rows) { + this.rows.clear(); + this.rows.addAll(rows); + + setRowCount(rows.size()); + setColumnCount(rowCount == 0 ? 0 : this.rows.get(0).size()); + } + + /** + * Sets the resizable state of all rows. If a bit is set to true in the + * BitSet, it means the row is resizable. + * + * The {@link BitSet#length() } must be equal to the {@link #getRowCount() } + * + * @param resizableRow + */ + public void setResizableRows(BitSet resizableRow) { + this.resizableRow = resizableRow; + } + + /** {@inheritDoc} */ + @Override + public boolean isRowResizable(int row) { + return resizableRow.get(row); + } + + /** {@inheritDoc} */ + @Override + public <E extends GridChange> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { + eventHandlerManager.addEventHandler(eventType, eventHandler); + } + + /** {@inheritDoc} */ + @Override + public <E extends GridChange> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { + eventHandlerManager.removeEventHandler(eventType, eventHandler); + } + + /** {@inheritDoc} */ + @Override + public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + return tail.append(eventHandlerManager); + } + + /*************************************************************************** + * + * Private implementation + * + **************************************************************************/ + + /** + * Set a new rowCount for the grid. + * + * @param rowCount + */ + private void setRowCount(int rowCount) { + this.rowCount = rowCount; + } + + /** + * Set a new columnCount for the grid. + * + * @param columnCount + */ + private void setColumnCount(int columnCount) { + this.columnCount = columnCount; + } + + /** + * This class serves as a bridge between row height Callback needed by the + * GridBase and a Map<Integer,Double> that one could have (each Integer + * specify a row index and its associated height). + */ + public static class MapBasedRowHeightFactory implements Callback<Integer, Double> { + private final Map<Integer, Double> rowHeightMap; + + public MapBasedRowHeightFactory(Map<Integer, Double> rowHeightMap) { + this.rowHeightMap = rowHeightMap; + } + + @Override + public Double call(Integer index) { + Double value = rowHeightMap.get(index); + return value == null ? AUTOFIT : value; + } + + } +} diff --git a/src/org/controlsfx/control/spreadsheet/GridChange.java b/src/org/controlsfx/control/spreadsheet/GridChange.java new file mode 100644 index 0000000000000000000000000000000000000000..c8ee2ffaccf0ee574e9d7462059ec195fceed450 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/GridChange.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. * Redistributions in binary + * form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided + * with the distribution. * Neither the name of ControlsFX, any associated + * website, nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import java.io.Serializable; + +import javafx.event.Event; +import javafx.event.EventType; + +/** + * This class represents a single change happening in a {@link Grid}. + * + * @see Grid + * @see GridBase + */ +public class GridChange extends Event implements Serializable { + + /** + * This is the event used by {@link GridChange}. + */ + public static final EventType<GridChange> GRID_CHANGE_EVENT = new EventType<>(Event.ANY, "GridChange"); //$NON-NLS-1$ + + /** + * ************************************************************************* + * * Static field * * + ************************************************************************* + */ + private static final long serialVersionUID = 210644901287223524L; + + /** + * ************************************************************************* + * * Private Fields * * + ************************************************************************* + */ + private final int row; + private final int column; + private final Object oldValue; + private final Object newValue; + + /** + * ************************************************************************* + * * Constructor * + ************************************************************************* + */ + /** + * Constructor of a GridChange when a change inside a + * {@link SpreadsheetCell} is happening. + * + * @param row + * @param column + * @param oldValue + * @param newValue + */ + public GridChange(int row, int column, Object oldValue, Object newValue) { + super(GRID_CHANGE_EVENT); + this.row = row; + this.column = column; + this.oldValue = oldValue; + this.newValue = newValue; + } + + /** + * ************************************************************************* + * * Public methods * * + ************************************************************************* + */ + /** + * Return the row number of this change. + * + * @return the row number of this change. + */ + public int getRow() { + return row; + } + + /** + * Return the column number of this change. + * + * @return the column number of this change. + */ + public int getColumn() { + return column; + } + + /** + * Return the value before the change. + * + * @return the value before the change. + */ + public Object getOldValue() { + return oldValue; + } + + /** + * Return the value after the change. + * + * @return the value after the change. + */ + public Object getNewValue() { + return newValue; + } +} diff --git a/src/org/controlsfx/control/spreadsheet/Picker.java b/src/org/controlsfx/control/spreadsheet/Picker.java new file mode 100644 index 0000000000000000000000000000000000000000..0c2762da12248a7376f5f69a7e0f566f6747d05b --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/Picker.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import java.util.Collection; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * + * Pickers can display some Images next to the headers. <br> + * You can specify the image by providing custom StyleClass :<br> + * + * <pre> + * .picker-label{ + * -fx-graphic: url("add.png"); + * -fx-background-color: white; + * -fx-padding: 0 0 0 0; + * } + * </pre> + * + * The {@link #onClick() } method does nothing by default, so you can override it + * if you want to execute a custom action when the user will click on your Picker. + * + * <h3>Visual:</h3> <center><img src="pickers.PNG" alt="Screenshot of Picker"></center> + * + */ +public class Picker { + + private final ObservableList<String> styleClass = FXCollections.observableArrayList(); + + /** + * Default constructor, the default "picker-label" styleClass is applied. + */ + public Picker() { + this("picker-label"); //$NON-NLS-1$ + } + + /** + * Initialize this Picker with the style classes provided. + * @param styleClass + */ + public Picker(String... styleClass) { + this.styleClass.addAll(styleClass); + } + + /** + * Initialize this Picker with the style classes provided. + * @param styleClass + */ + public Picker(Collection<String> styleClass) { + this.styleClass.addAll(styleClass); + } + + + /** + * @return the style class of this picker. + */ + public final ObservableList<String> getStyleClass() { + return styleClass; + } + + /** + * This method will be called whenever the user clicks on this picker. + */ + public void onClick(){ + //no-op by default + } +} diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetCell.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetCell.java new file mode 100644 index 0000000000000000000000000000000000000000..29bb9ad7cd645418a15947000a53b2d2741e4976 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetCell.java @@ -0,0 +1,333 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import java.util.Optional; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.ObservableSet; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.scene.Node; + +/** + * + * Interface of the cells used in the {@link SpreadsheetView}. + * + * See {@link SpreadsheetCellBase} for a complete and detailed documentation. + * @see SpreadsheetCellBase + */ +public interface SpreadsheetCell { + /** + * This EventType can be used with an {@link EventHandler} in order to catch + * when the editable state of a SpreadsheetCell is changed. + */ + public static final EventType EDITABLE_EVENT_TYPE = new EventType("EditableEventType"); //$NON-NLS-1$ + + /** + * This EventType can be used with an {@link EventHandler} in order to catch + * when the wrap text state of a SpreadsheetCell is changed. + */ + public static final EventType WRAP_EVENT_TYPE = new EventType("WrapTextEventType"); //$NON-NLS-1$ + + /** + * This EventType can be used with an {@link EventHandler} in order to catch + * when a corner state of a SpreadsheetCell is changed. + */ + public static final EventType CORNER_EVENT_TYPE = new EventType("CornerEventType"); //$NON-NLS-1$ + + /** + * This enum states the four different corner available for positioning + * some elements in a cell. + */ + public static enum CornerPosition { + + TOP_LEFT , + TOP_RIGHT , + BOTTOM_RIGHT , + BOTTOM_LEFT + } + + /** + * Verify that the upcoming cell value can be set to the current cell. This + * is currently used by the Copy/Paste. + * + * @param cell + * @return true if the upcoming cell value can be set to the current cell. + */ + public boolean match(SpreadsheetCell cell); + + /** + * Sets the value of the property Item. This should be used only at + * initialization. Prefer {@link Grid#setCellValue(int, int, Object)} after + * because it will compute correctly the modifiedCell. If + * {@link #isEditable()} return false, nothing is done. + * + * @param value + */ + public void setItem(Object value); + + /** + * Return the value contained in the cell. + * + * @return the value contained in the cell. + */ + public Object getItem(); + + /** + * The item property represents the currently-set value inside this + * SpreadsheetCell instance. + * + * @return the item property which contains the value. + */ + public ObjectProperty<Object> itemProperty(); + + /** + * Return if this cell can be edited or not. + * + * @return true if this cell is editable. + */ + public boolean isEditable(); + + /** + * Change the editable state of this cell + * + * @param editable + */ + public void setEditable(boolean editable); + + /** + * If a run of text exceeds the width of the Labeled, then this variable + * indicates whether the text should wrap onto another line. + * + * @return the value of wrapText property. + */ + public boolean isWrapText(); + + /** + * If a run of text exceeds the width of the Labeled, then this variable + * indicates whether the text should wrap onto another line. + * @param wrapText + */ + public void setWrapText(boolean wrapText); + + /** + * A string representation of the CSS style associated with this specific + * Node. This is analogous to the "style" attribute of an HTML element. Note + * that, like the HTML style attribute, this variable contains style + * properties and values and not the selector portion of a style rule. + * + * @param style + */ + public void setStyle(String style); + + /** + * A string representation of the CSS style associated with this specific + * Node. This is analogous to the "style" attribute of an HTML element. Note + * that, like the HTML style attribute, this variable contains style + * properties and values and not the selector portion of a style rule. + * + * @return The inline CSS style associated with this Node. If this Node does + * not have an inline style, an empty String is returned. + */ + public String getStyle(); + + /** + * A string representation of the CSS style associated with this specific + * Node. This is analogous to the "style" attribute of an HTML element. Note + * that, like the HTML style attribute, this variable contains style + * properties and values and not the selector portion of a style rule. + * + * @return a string representation of the CSS style + */ + public StringProperty styleProperty(); + + /** + * This activate the given cornerPosition. + * @param position + */ + public void activateCorner(CornerPosition position); + + /** + * This deactivate the given cornerPosition. + * @param position + */ + public void deactivateCorner(CornerPosition position); + + /** + * + * @param position + * @return the current state of a specific corner. + */ + public boolean isCornerActivated(CornerPosition position); + + /** + * The {@link StringProperty} linked with the format. + * + * @return The {@link StringProperty} linked with the format state. + */ + public StringProperty formatProperty(); + + /** + * Return the format of this cell or an empty string if no format has been + * specified. + * + * @return Return the format of this cell or an empty string if no format + * has been specified. + */ + public String getFormat(); + + /** + * Set a new format for this Cell. You can specify how to represent the + * value in the cell. + * + * @param format + */ + public void setFormat(String format); + + /** + * Return the StringProperty of the representation of the value. + * + * @return the StringProperty of the representation of the value. + */ + public ReadOnlyStringProperty textProperty(); + + /** + * Return the String representation currently used for display in the + * {@link SpreadsheetView}. + * + * @return text representation of the value. + */ + public String getText(); + + /** + * Return the {@link SpreadsheetCellType} of this particular cell. + * + * @return the {@link SpreadsheetCellType} of this particular cell. + */ + public SpreadsheetCellType getCellType(); + + /** + * Return the row of this cell. + * + * @return the row of this cell. + */ + public int getRow(); + + /** + * Return the column of this cell. + * + * @return the column of this cell. + */ + public int getColumn(); + + /** + * Return how much this cell is spanning in row, 1 is normal. + * + * @return how much this cell is spanning in row, 1 is normal. + */ + public int getRowSpan(); + + /** + * Sets how much this cell is spanning in row. See {@link SpreadsheetCell} + * description for information. You should use + * {@link Grid#spanRow(int, int, int)} instead of using this method + * directly. + * + * @param rowSpan + */ + public void setRowSpan(int rowSpan); + + /** + * Return how much this cell is spanning in column, 1 is normal. + * + * @return how much this cell is spanning in column, 1 is normal. + */ + public int getColumnSpan(); + + /** + * Sets how much this cell is spanning in column. See + * {@link SpreadsheetCell} description for information. You should use + * {@link Grid#spanColumn(int, int, int)} instead of using this method + * directly. + * + * @param columnSpan + */ + public void setColumnSpan(int columnSpan); + + /** + * Return an ObservableList of String of all the style class associated with + * this cell. You can easily modify its appearance by adding a style class + * (previously set in CSS). + * + * @return an ObservableList of String of all the style class + */ + public ObservableSet<String> getStyleClass(); + + /** + * @return an ObjectProperty wrapping a Node for the graphic. + */ + public ObjectProperty<Node> graphicProperty(); + + /** + * Set a graphic for this cell to display aside with the text. + * + * @param graphic + */ + public void setGraphic(Node graphic); + + /** + * Return the graphic node associated with this cell. Return null if nothing + * has been associated. + * + * @return the graphic node associated with this cell. + */ + public Node getGraphic(); + + /** + * @return the tooltip associated with this SpreadsheetCell. + */ + public Optional<String> getTooltip(); + + /** + * Registers an event handler to this SpreadsheetCell. + * @param eventType the type of the events to receive by the handler + * @param eventHandler the handler to register + * @throws NullPointerException if the event type or handler is null + */ + public void addEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler); + + /** + * Unregisters a previously registered event handler from this SpreadsheetCell. + * @param eventType the event type from which to unregister + * @param eventHandler the handler to unregister + * @throws NullPointerException if the event type or handler is null + */ + public void removeEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler); +} diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetCellBase.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetCellBase.java new file mode 100644 index 0000000000000000000000000000000000000000..f13634f36c40d4a569ff38646dfac194b67f4600 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetCellBase.java @@ -0,0 +1,643 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import com.sun.javafx.event.EventHandlerManager; +import java.util.Objects; +import java.util.Optional; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableSet; +import javafx.event.Event; +import javafx.event.EventDispatchChain; +import javafx.event.EventHandler; +import javafx.event.EventTarget; +import javafx.event.EventType; +import javafx.scene.Node; +import javafx.scene.image.ImageView; + +/** + * The SpreadsheetCells serve as model for the {@link SpreadsheetView}. <br> + * You will provide them when constructing a {@link Grid}. + * + * <br> + * <h3>SpreadsheetCell Types</h3> Each SpreadsheetCell has its own + * {@link SpreadsheetCellType} which has its own {@link SpreadsheetCellEditor} + * in order to control very closely the possible modifications. + * + * <p> + * Different {@link SpreadsheetCellType SpreadsheetCellTypes} are available + * depending on the data you want to represent in your {@link SpreadsheetView}. + * You can use the different static method provided in + * {@link SpreadsheetCellType} in order to create the specialized + * SpreadsheetCell that suits your need. + * + * + * <br> + * + * <p> + * If you want to create a SpreadsheetCell of your own, you simply have to + * use one of the provided constructor. Usually you will let your {@link SpreadsheetCellType} + * create the cells. For example + * {@link SpreadsheetCellType.StringType#createCell(int, int, int, int, java.lang.String) }. + * You will also have to provide a custom {@link SpreadsheetCellEditor}. + * + * <h2>Configuration</h2> + * You will have to indicate the coordinates of the cell together with the + * {@link #setRowSpan(int) row} and {@link #setColumnSpan(int) column} span. You + * can specify if you want the cell to be editable or not using + * {@link #setEditable(boolean)}. Be advised that a cell with a rowSpan means + * that the cell will replace all the cells situated in the rowSpan range. Same + * with the column span. + * <br> + * So the best way to handle spanning is to fill your grid + * with unique cells, and then call at the end {@link GridBase#spanColumn(int, int, int)} + * or {@link GridBase#spanRow(int, int, int)}. These methods will handle the span + * for you. + * + * <br> + * + * <h3>Format</h3> + * Your cell can have its very own format. If you want to display some dates + * with different format, you just have to create a unique + * {@link SpreadsheetCellType} and then specify for each cell their format with + * {@link #setFormat(String)}. You will then have the guaranty that all your + * cells will have a LocalDate as a value, but the value will be displayed + * differently for each cell. This will also guaranty that copy/paste and other + * operation will be compatible since every cell will share the same + * {@link SpreadsheetCellType}. <br> + * Here an example : <br> + * + * + * <pre> + * SpreadsheetCell cell = SpreadsheetCellType.DATE.createCell(row, column, rowSpan, colSpan, + * LocalDate.now().plusDays((int) (Math.random() * 10))); // Random value + * // given here + * final double random = Math.random(); + * if (random < 0.25) { + * cell.setFormat("EEEE d"); + * } else if (random < 0.5) { + * cell.setFormat("dd/MM :YY"); + * } else { + * cell.setFormat("dd/MM/YYYY"); + * } + * </pre> + * + * <center><img src="dateFormat.PNG" alt="SpreadsheetCellBase with custom format"></center> + * + * <h3>Graphic</h3> + * Each cell can have a graphic to display next to the text in the cells. Just + * use the {@link #setGraphic(Node)} in order to specify the graphic you want. + * If you specify an {@link ImageView}, the SpreadsheetView will try to resize it in + * order to fit the space available in the cell. + * + * For example : + * + * <pre> + * cell.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("icons/exclamation.png")))); + * </pre> + * + * <center><img src="graphicNodeToCell.png" alt="SpreadsheetCellBase with graphic"></center> <br> + * In addition to that, you can also specify another graphic property to your + * cell with {@link #activateCorner(org.controlsfx.control.spreadsheet.SpreadsheetCell.CornerPosition) }. + * This allow you to activate or deactivate some graphics on the cell in every + * corner. Right now it's a little red triangle but you can modify this in your CSS by + * using the "<b>cell-corner</b>" style class. + * + * <pre> + * .cell-corner.top-left{ + * -fx-background-color: red; + * -fx-shape : "M 0 0 L 1 0 L 0 1 z"; + * } + * </pre> + * + * <center><img src="triangleCell.PNG" alt="SpreadsheetCellBase with a styled cell-corner"></center> + * + * + * <br> + * You can also customize the tooltip of your SpreadsheetCell by specifying one + * with {@link #setTooltip(java.lang.String) }. + * + * <h3>Style with CSS</h3> + * You can style your cell by specifying some styleClass with + * {@link #getStyleClass()}. You just have to create and custom that class in + * your CSS stylesheet associated with your {@link SpreadsheetView}. Also note + * that all {@link SpreadsheetCell} have a "<b>spreadsheet-cell</b>" styleClass + * added by default. Here is a example :<br> + * + * <pre> + * cell.getStyleClass().add("row_header"); + * </pre> + * + * And in the CSS: + * + * <pre> + * .spreadsheet-cell.row_header{ + * -fx-background-color: #b4d4ad ; + * -fx-background-insets: 0, 0 1 1 0; + * -fx-alignment: center; + * } + * </pre> + * + * <h3>Examples</h3> + * Here is an example that uses all the pre-built {@link SpreadsheetCellType} + * types. The generation is random here so you will want to replace the logic to + * suit your needs. + * + * <pre> + * private SpreadsheetCell<?> generateCell(int row, int column, int rowSpan, int colSpan) { + * List<String> stringListTextCell = Arrays.asList("Shanghai","Paris","New York City","Bangkok","Singapore","Johannesburg","Berlin","Wellington","London","Montreal"); + * final double random = Math.random(); + * if (random < 0.10) { + * List<String> stringList = Arrays.asList("China","France","New Zealand","United States","Germany","Canada"); + * cell = SpreadsheetCellType.LIST(stringList).createCell(row, column, rowSpan, colSpan, stringList.get((int) (Math.random() * 6))); + * } else if (random >= 0.10 && random < 0.25) { + * cell = SpreadsheetCellType.STRING.createCell(row, column, rowSpan, colSpan,stringListTextCell.get((int)(Math.random()*10))); + * } else if (random >= 0.25 && random < 0.75) { + * cell = SpreadsheetCellType.DOUBLE.createCell(row, column, rowSpan, colSpan,(double)Math.round((Math.random()*100)*100)/100); + * } else { + * cell = SpreadsheetCellType.DATE.createCell(row, column, rowSpan, colSpan, LocalDate.now().plusDays((int)(Math.random()*10))); + * } + * return cell; + * } + * </pre> + * + * @see SpreadsheetView + * @see SpreadsheetCellEditor + * @see SpreadsheetCellType + */ +public class SpreadsheetCellBase implements SpreadsheetCell, EventTarget{ + + /*************************************************************************** + * + * Private Fields + * + **************************************************************************/ + + //The Bit position for the editable Property. + private static final int EDITABLE_BIT_POSITION = 4; + private static final int WRAP_BIT_POSITION = 5; + private final SpreadsheetCellType type; + private final int row; + private final int column; + private int rowSpan; + private int columnSpan; + private final StringProperty format; + private final StringProperty text; + private final StringProperty styleProperty; + private final ObjectProperty<Node> graphic; + private String tooltip; + /** + * This variable handles all boolean values of this SpreadsheetCell inside + * its bits. Instead of using regular boolean, we use that int so that we + * can reduce memory usage to the bare minimum. + */ + private int propertyContainer = 0; + private final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); + + private ObservableSet<String> styleClass; + + /*************************************************************************** + * + * Constructor + * + **************************************************************************/ + + /** + * Constructs a SpreadsheetCell with the given configuration. + * Use the {@link SpreadsheetCellType#OBJECT} type. + * @param row + * @param column + * @param rowSpan + * @param columnSpan + */ + public SpreadsheetCellBase(final int row, final int column, final int rowSpan, final int columnSpan) { + this(row, column, rowSpan, columnSpan, SpreadsheetCellType.OBJECT); + } + + /** + * Constructs a SpreadsheetCell with the given configuration. + * + * @param row + * @param column + * @param rowSpan + * @param columnSpan + * @param type + */ + public SpreadsheetCellBase(final int row, final int column, final int rowSpan, final int columnSpan, + final SpreadsheetCellType<?> type) { + this.row = row; + this.column = column; + this.rowSpan = rowSpan; + this.columnSpan = columnSpan; + this.type = type; + text = new SimpleStringProperty(""); //$NON-NLS-1$ + format = new SimpleStringProperty(""); //$NON-NLS-1$ + graphic = new SimpleObjectProperty<>(); + format.addListener(new ChangeListener<String>() { + @Override + public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) { + updateText(); + } + }); + //Editable is true at the initialisation + setEditable(true); + getStyleClass().add("spreadsheet-cell"); //$NON-NLS-1$ + styleProperty = new SimpleStringProperty(); + } + + /*************************************************************************** + * + * Public Methods + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override + public boolean match(SpreadsheetCell cell) { + return type.match(cell); + } + + // --- item + private final ObjectProperty<Object> item = new SimpleObjectProperty<Object>(this, "item") { //$NON-NLS-1$ + @Override + protected void invalidated() { + updateText(); + } + }; + + /** {@inheritDoc} */ + @Override + public final void setItem(Object value) { + if (isEditable()) + item.set(value); + } + + /** {@inheritDoc} */ + @Override + public final Object getItem() { + return item.get(); + } + + /** {@inheritDoc} */ + @Override + public final ObjectProperty<Object> itemProperty() { + return item; + } + + /** {@inheritDoc} */ + @Override + public final boolean isEditable() { + return isSet(EDITABLE_BIT_POSITION); + } + + /** {@inheritDoc} */ + @Override + public final void setEditable(boolean editable) { + if(setMask(editable, EDITABLE_BIT_POSITION)){ + Event.fireEvent(this, new Event(EDITABLE_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isWrapText(){ + return isSet(WRAP_BIT_POSITION); + } + + /** {@inheritDoc} */ + @Override + public void setWrapText(boolean wrapText) { + if (setMask(wrapText, WRAP_BIT_POSITION)) { + Event.fireEvent(this, new Event(WRAP_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public final StringProperty formatProperty() { + return format; + } + + /** {@inheritDoc} */ + @Override + public final String getFormat() { + return format.get(); + } + + /** {@inheritDoc} */ + @Override + public final void setFormat(String format) { + formatProperty().set(format); + updateText(); + } + + /** {@inheritDoc} */ + @Override + public final ReadOnlyStringProperty textProperty() { + return text; + } + + /** {@inheritDoc} */ + @Override + public final String getText() { + return text.get(); + } + + /** {@inheritDoc} */ + @Override + public final SpreadsheetCellType getCellType() { + return type; + } + + /** {@inheritDoc} */ + @Override + public final int getRow() { + return row; + } + + /** {@inheritDoc} */ + @Override + public final int getColumn() { + return column; + } + + /** {@inheritDoc} */ + @Override + public final int getRowSpan() { + return rowSpan; + } + + /** {@inheritDoc} */ + @Override + public final void setRowSpan(int rowSpan) { + this.rowSpan = rowSpan; + } + + /** {@inheritDoc} */ + @Override + public final int getColumnSpan() { + return columnSpan; + } + + /** {@inheritDoc} */ + @Override + public final void setColumnSpan(int columnSpan) { + this.columnSpan = columnSpan; + } + + /** {@inheritDoc} */ + @Override + public final ObservableSet<String> getStyleClass() { + if (styleClass == null) { + styleClass = FXCollections.observableSet(); + } + return styleClass; + } + + /** {@inheritDoc} */ + @Override + public void setStyle(String style){ + styleProperty.set(style); + } + + /** {@inheritDoc} */ + @Override + public String getStyle(){ + return styleProperty.get(); + } + + /** {@inheritDoc} */ + @Override + public StringProperty styleProperty(){ + return styleProperty; + } + + /** {@inheritDoc} */ + @Override + public ObjectProperty<Node> graphicProperty() { + return graphic; + } + + /** {@inheritDoc} */ + @Override + public void setGraphic(Node graphic) { + this.graphic.set(graphic); + } + + /** {@inheritDoc} */ + @Override + public Node getGraphic() { + return graphic.get(); + } + + /** {@inheritDoc} */ + @Override + public Optional<String> getTooltip() { + return Optional.ofNullable(tooltip); + } + + /** + * Set a new tooltip for this cell. + * @param tooltip + */ + public void setTooltip(String tooltip){ + this.tooltip = tooltip; + } + + /** {@inheritDoc} */ + @Override + public void activateCorner(CornerPosition position) { + if(setMask(true, getCornerBitNumber(position))){ + Event.fireEvent(this, new Event(CORNER_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public void deactivateCorner(CornerPosition position) { + if(setMask(false, getCornerBitNumber(position))){ + Event.fireEvent(this, new Event(CORNER_EVENT_TYPE)); + } + } + + /** {@inheritDoc} */ + @Override + public boolean isCornerActivated(CornerPosition position) { + return isSet(getCornerBitNumber(position)); + } + + /** {@inheritDoc} */ + @Override + public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + return tail.append(eventHandlerManager); + } + + /*************************************************************************** + * + * Overridden Methods + * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override + public String toString() { + return "cell[" + row + "][" + column + "]" + rowSpan + "-" + columnSpan; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof SpreadsheetCell)) + return false; + + final SpreadsheetCell otherCell = (SpreadsheetCell) obj; + return otherCell.getRow() == row && otherCell.getColumn() == column + && Objects.equals(otherCell.getText(), getText()) + && rowSpan == otherCell.getRowSpan() + && columnSpan == otherCell.getColumnSpan() + && Objects.equals(getStyleClass(), otherCell.getStyleClass()); + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + column; + result = prime * result + row; + result = prime * result + rowSpan; + result = prime * result + columnSpan; + result = prime * result + Objects.hashCode(getText()); + result = prime * result + Objects.hashCode(getStyleClass()); + return result; + } + + /** + * Registers an event handler to this SpreadsheetCell. The SpreadsheetCell class allows + * registration of listeners which will be notified when a corner state of + * the editable state of this SpreadsheetCell have changed. + * + * @param eventType the type of the events to receive by the handler + * @param eventHandler the handler to register + * @throws NullPointerException if the event type or handler is null + */ + @Override + public void addEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler) { + eventHandlerManager.addEventHandler(eventType, eventHandler); + } + + /** + * Unregisters a previously registered event handler from this SpreadsheetCell. One + * handler might have been registered for different event types, so the + * caller needs to specify the particular event type from which to + * unregister the handler. + * + * @param eventType the event type from which to unregister + * @param eventHandler the handler to unregister + * @throws NullPointerException if the event type or handler is null + */ + @Override + public void removeEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler) { + eventHandlerManager.removeEventHandler(eventType, eventHandler); + } + + /*************************************************************************** + * + * Private Implementation + * + **************************************************************************/ + + /** + * Update the text for the SpreadsheetView. + */ + @SuppressWarnings("unchecked") + private void updateText() { + if(getItem() == null){ + text.setValue(""); //$NON-NLS-1$ + }else if (!("").equals(getFormat())) { //$NON-NLS-1$ + text.setValue(type.toString(getItem(), getFormat())); + } else { + text.setValue(type.toString(getItem())); + } + } + + /** + * Return the Bit position for each corner. + * @param position + * @return + */ + private int getCornerBitNumber(CornerPosition position) { + switch (position) { + case TOP_LEFT: + return 0; + + case TOP_RIGHT: + return 1; + + case BOTTOM_RIGHT: + return 2; + + case BOTTOM_LEFT: + default: + return 3; + } + } + + /** + * Set the specified bit position at the value specified by flag. + * @param flag + * @param position + * @return whether a change has really occured. + */ + private boolean setMask(boolean flag, int position) { + int oldCorner = propertyContainer; + if (flag) { + propertyContainer |= (1 << position); + } else { + propertyContainer &= ~(1 << position); + } + return propertyContainer != oldCorner; + } + + /** + * @param mask + * @param position + * @return whether the specified bit position is true. + */ + private boolean isSet(int position) { + return (propertyContainer & (1 << position)) != 0; + } +} diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetCellEditor.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetCellEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..2c4aec7e60a6923329f69d2c95ddb84f66ff6a2b --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetCellEditor.java @@ -0,0 +1,927 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import impl.org.controlsfx.i18n.Localization; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.time.LocalDate; +import java.util.List; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Control; +import javafx.scene.control.DatePicker; +import javafx.scene.control.IndexRange; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.util.StringConverter; + +/** + * + * SpreadsheetCellEditor are used by {@link SpreadsheetCellType} and + * {@link SpreadsheetCell} in order to control how each value will be entered. <br> + * + * <h3>General behavior:</h3> Editors will be displayed if the user double-click + * in an editable cell ( see {@link SpreadsheetCell#setEditable(boolean)} ). <br> + * If the user does anything outside the editor, the editor <b> will be forced + * </b> to try to commit the edition and close itself. If the value is not + * valid, the editor will cancel the value and close itself. The editor is just + * here to allow communication between the user and the {@link SpreadsheetView}. + * It will just be given a value, and it will just give back another one after. + * The policy regarding validation of a given value is defined in + * {@link SpreadsheetCellType#match(Object)}. + * + * If the value doesn't meet the requirements when saving the cell, nothing + * happens and the editor keeps editing. <br> + * You can abandon a current modification by pressing "esc" key. <br> + * + * You can specify a maximum height to your spreadsheetCellEditor with {@link #getMaxHeight() + * }. This can be used in order to control the display of your editor. If they + * should grow or not in a big cell. (for example a {@link TextAreaEditor} want + * to grow with the cell in order to take full space for display. + * <br> + * <h3>Specific behavior:</h3> This class offers some static classes in order to + * create a {@link SpreadsheetCellEditor}. Here are their properties: <br> + * + * <ul> + * <li> {@link StringEditor}: Basic {@link TextField}, can accept all data and + * save it as a string.</li> + * <li> {@link ListEditor}: Display a {@link ComboBox} with the different values. + * </li> + * <li> {@link DoubleEditor}: Display a {@link TextField} which accepts only + * double value. If the entered value is incorrect, the background will turn red + * so that the user will know in advance if the data will be saved or not.</li> + * <li> {@link IntegerEditor}: Display a {@link TextField} which accepts only + * Integer value. If the entered value is incorrect, the background will turn red + * so that the user will know in advance if the data will be saved or not.</li> + * <li> {@link DateEditor}: Display a {@link DatePicker}.</li> + * <li> {@link ObjectEditor}: Display a {@link TextField} , accept an Object.</li> + * </ul> + * + * <br> + * <h3>Creating your editor:</h3> You can of course create your own + * {@link SpreadsheetCellEditor} for displaying other controls.<br> + * + * You just have to override the four abstract methods. <b>Remember</b> that you + * will never call those methods directly. They will be called by the + * {@link SpreadsheetView} when needed. + * <ul> + * <li> {@link #startEdit(Object)}: You will configure your control with the + * given value which is {@link SpreadsheetCell#getItem()} converted to an + * object. You do not instantiate your control here, you do it in the + * constructor.</li> + * <li> {@link #getEditor()}: You will return which control you're using (for + * display).</li> + * <li> {@link #getControlValue()}: You will return the value inside your editor + * in order to submit it for validation.</li> + * <li> {@link #end()}: When editing is finished, you can properly close your own + * control.</li> + * </ul> + * <br> + * Keep in mind that you will interact only with {@link #endEdit(boolean)} where + * a <b>true</b> value means you want to commit, and a <b>false</b> means you + * want to cancel. The {@link SpreadsheetView} will handle all the rest for you + * and call your methods at the right moment. <br> + * + * <h3>Use case :</h3> <center><img src="editorScheme.png" alt="Use case of SpreadsheetCellEditor"></center> + * + * <h3>Visual:</h3> + * <table style="border: 1px solid gray;" summary="Screenshots of various SpreadsheetCellEditor"> + * <tr> + * <td valign="center" style="text-align:right;"><strong>String</strong></td> + * <td><center><img src="textEditor.png" alt="Screenshot of SpreadsheetCellEditor.StringEditor"></center></td> + * </tr> + * <tr> + * <td valign="center" style="text-align:right;"><strong>List</strong></td> + * <td><center><img src="listEditor.png" alt="Screenshot of SpreadsheetCellEditor.ListEditor"></center></td> + * </tr> + * <tr> + * <td valign="center" style="text-align:right;"><strong>Double</strong></td> + * <td><center><img src="doubleEditor.png" alt="Screenshot of SpreadsheetCellEditor.DoubleEditor"></center></td> + * </tr> + * <tr> + * <td valign="center" style="text-align:right;"><strong>Date</strong></td> + * <td><center><img src="dateEditor.png" alt="Screenshot of SpreadsheetCellEditor.DateEditor"></center></td> + * </tr> + * </table> + * + * + * @see SpreadsheetView + * @see SpreadsheetCell + * @see SpreadsheetCellType + */ +public abstract class SpreadsheetCellEditor { + private static final double MAX_EDITOR_HEIGHT = 50.0; + + private static final DecimalFormat decimalFormat = new DecimalFormat("#.##########"); //$NON-NLS-1$ + SpreadsheetView view; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + + /** + * Construct the SpreadsheetCellEditor. + * + * @param view + */ + public SpreadsheetCellEditor(SpreadsheetView view) { + this.view = view; + } + + /*************************************************************************** + * * Public Final Methods * * + **************************************************************************/ + /** + * Whenever you want to stop the edition, you call that method.<br> + * True means you're trying to commit the value, then + * {@link SpreadsheetCellType#convertValue(Object)} will be called in order + * to verify that the value is correct.<br> + * False means you're trying to cancel the value and it will be follow by + * {@link #end()}.<br> + * See SpreadsheetCellEditor description + * + * @param b + * true means commit, false means cancel + */ + public final void endEdit(boolean b) { + view.getCellsViewSkin().getSpreadsheetCellEditorImpl().endEdit(b); + } + + /*************************************************************************** + * * Public Abstract Methods * * + **************************************************************************/ + /** + * This method will be called when edition start.<br> + * You will then do all the configuration of your editor. + * + * @param item + */ + public abstract void startEdit(Object item); + + /** + * Return the control used for controlling the input. This is called at the + * beginning in order to display your control in the cell. + * + * @return the control used. + */ + public abstract Control getEditor(); + + /** + * Return the value within your editor as a string. This will be used by the + * {@link SpreadsheetCellType#convertValue(Object)} in order to compute + * whether the value is valid regarding the {@link SpreadsheetCellType} + * policy. + * + * @return the value within your editor as a string. + */ + public abstract String getControlValue(); + + /** + * This method will be called at the end of edition.<br> + * You will be offered the possibility to do the configuration post editing. + */ + public abstract void end(); + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + /** + * Return the maximum height of the editor. + * @return 50 by default. + */ + public double getMaxHeight(){ + return MAX_EDITOR_HEIGHT; + } + + /** + * A {@link SpreadsheetCellEditor} for + * {@link SpreadsheetCellType.ObjectType} typed cells. It displays a + * {@link TextField} where the user can type different values. + */ + public static class ObjectEditor extends SpreadsheetCellEditor { + + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final TextField tf; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + /** + * Constructor for the ObjectEditor.. + * @param view The SpreadsheetView + */ + public ObjectEditor(SpreadsheetView view) { + super(view); + tf = new TextField(); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + @Override + public void startEdit(Object value) { + if(value == null) tf.setText(""); + else if (value instanceof String) { + tf.setText(value.toString()); + } + attachEnterEscapeEventHandler(); + + tf.requestFocus(); + tf.end(); + } + + @Override + public String getControlValue() { + return tf.getText(); + } + + @Override + public void end() { + tf.setOnKeyPressed(null); + } + + @Override + public TextField getEditor() { + return tf; + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + private void attachEnterEscapeEventHandler() { + tf.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ENTER) { + endEdit(true); + } else if (t.getCode() == KeyCode.ESCAPE) { + endEdit(false); + } + } + }); + } + } + + /** + * A {@link SpreadsheetCellEditor} for + * {@link SpreadsheetCellType.StringType} typed cells. It displays a + * {@link TextField} where the user can type different values. + */ + public static class StringEditor extends SpreadsheetCellEditor { + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final TextField tf; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + /** + * Constructor for the StringEditor. + * @param view The SpreadsheetView + */ + public StringEditor(SpreadsheetView view) { + super(view); + tf = new TextField(); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + @Override + public void startEdit(Object value) { + + if (value instanceof String || value == null) { + tf.setText((String) value); + } + attachEnterEscapeEventHandler(); + + tf.requestFocus(); + tf.selectAll(); + } + + @Override + public String getControlValue() { + return tf.getText(); + } + + @Override + public void end() { + tf.setOnKeyPressed(null); + } + + @Override + public TextField getEditor() { + return tf; + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + private void attachEnterEscapeEventHandler() { + tf.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ENTER) { + endEdit(true); + } else if (t.getCode() == KeyCode.ESCAPE) { + endEdit(false); + } + } + }); + } + } + + + /** + * A {@link SpreadsheetCellEditor} for + * {@link SpreadsheetCellType.StringType} typed cells. It displays a + * {@link TextField} where the user can type different values. + */ + public static class TextAreaEditor extends SpreadsheetCellEditor { + + /** + * ************************************************************************* + * * Private Fields * * + * ************************************************************************ + */ + private final TextArea textArea; + + /** + * ************************************************************************* + * * Constructor * * + * ************************************************************************ + */ + /** + * Constructor for the StringEditor. + * + * @param view The SpreadsheetView + */ + public TextAreaEditor(SpreadsheetView view) { + super(view); + textArea = new TextArea(); + textArea.setWrapText(true); + //The textArea is not respecting the maxHeight if we are not setting the min.. + textArea.minHeightProperty().bind(textArea.maxHeightProperty()); + } + + /** + * ************************************************************************* + * * Public Methods * * + * ************************************************************************ + */ + @Override + public void startEdit(Object value) { + if (value instanceof String || value == null) { + textArea.setText((String) value); + } + attachEnterEscapeEventHandler(); + + textArea.requestFocus(); + textArea.selectAll(); + } + + @Override + public String getControlValue() { + return textArea.getText(); + } + + @Override + public void end() { + textArea.setOnKeyPressed(null); + } + + @Override + public TextArea getEditor() { + return textArea; + } + + @Override + public double getMaxHeight() { + return Double.MAX_VALUE; + } + + /** + * ************************************************************************* + * * Private Methods * * + * ************************************************************************ + */ + private void attachEnterEscapeEventHandler() { + + textArea.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent keyEvent) { + if (keyEvent.getCode() == KeyCode.ENTER) { + if (keyEvent.isShiftDown()) { + //if shift is down, we insert a new line. + textArea.replaceSelection("\n"); //$NON-NLS-1$ + } else { + endEdit(true); + } + } else if (keyEvent.getCode() == KeyCode.ESCAPE) { + endEdit(false); + }else if(keyEvent.getCode() == KeyCode.TAB){ + if (keyEvent.isShiftDown()) { + //if shift is down, we insert a tab. + textArea.replaceSelection("\t"); //$NON-NLS-1$ + keyEvent.consume(); + } else { + endEdit(true); + } + } + } + }); + } + } + + /** + * A {@link SpreadsheetCellEditor} for + * {@link SpreadsheetCellType.DoubleType} typed cells. It displays a + * {@link TextField} where the user can type different numbers. Only numbers + * will be stored. <br> + * Moreover, the {@link TextField} will turn red if the value currently + * entered if incorrect. + */ + public static class DoubleEditor extends SpreadsheetCellEditor { + + /*************************************************************************** + * * private Fields * * + **************************************************************************/ + private final TextField tf; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + /** + * Constructor for the DoubleEditor. + * @param view The SpreadsheetView. + */ + public DoubleEditor(SpreadsheetView view) { + super(view); + tf = new TextField() { + + @Override + public void insertText(int index, String text) { + String fixedText = fixText(text); + super.insertText(index, fixedText); + } + + @Override + public void replaceText(int start, int end, String text) { + String fixedText = fixText(text); + super.replaceText(start, end, fixedText); + } + + @Override + public void replaceText(IndexRange range, String text) { + replaceText(range.getStart(), range.getEnd(), text); + } + + private String fixText(String text) { + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Localization.getLocale()); + text = text.replace(' ', '\u00a0');//$NON-NLS-1$ + return text.replaceAll("\\.", Character.toString(symbols.getDecimalSeparator()));//$NON-NLS-1$ + } + }; + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + /** {@inheritDoc} */ + @Override + public void startEdit(Object value) { + if (value instanceof Double) { + //We want to set the text in its proper form regarding the Locale. + decimalFormat.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Localization.getLocale())); + tf.setText(((Double) value).isNaN() ? "" : decimalFormat.format(value)); //$NON-NLS-1$ + } else { + tf.setText(null); + } + + tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ + attachEnterEscapeEventHandler(); + + tf.requestFocus(); + tf.selectAll(); + } + + /** {@inheritDoc} */ + @Override + public void end() { + tf.setOnKeyPressed(null); + } + + /** {@inheritDoc} */ + @Override + public TextField getEditor() { + return tf; + } + + /** {@inheritDoc} */ + @Override + public String getControlValue() { + NumberFormat format = NumberFormat.getInstance(Localization.getLocale()); + ParsePosition parsePosition = new ParsePosition(0); + if (tf.getText() != null) { + Number number = format.parse(tf.getText(), parsePosition); + if (number != null && parsePosition.getIndex() == tf.getText().length()) { + return String.valueOf(number.doubleValue()); + } + } + return tf.getText(); + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + private void attachEnterEscapeEventHandler() { + tf.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ENTER) { + try { + if (tf.getText().equals("")) { //$NON-NLS-1$ + endEdit(true); + } else { + tryParsing(); + endEdit(true); + } + } catch (Exception e) { + } + + } else if (t.getCode() == KeyCode.ESCAPE) { + endEdit(false); + } + } + }); + + tf.setOnKeyReleased(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + try { + if (tf.getText().equals("")) { //$NON-NLS-1$ + tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ + } else { + tryParsing(); + tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ + } + } catch (Exception e) { + tf.getStyleClass().add("error"); //$NON-NLS-1$ + } + } + }); + } + + private void tryParsing() throws ParseException { + NumberFormat format = NumberFormat.getNumberInstance(Localization.getLocale()); + ParsePosition parsePosition = new ParsePosition(0); + format.parse(tf.getText(), parsePosition); + if (parsePosition.getIndex() != tf.getText().length()) { + throw new ParseException("Invalid input", parsePosition.getIndex()); + } + } + } + + /** + * A {@link SpreadsheetCellEditor} for + * {@link SpreadsheetCellType.DoubleType} typed cells. It displays a + * {@link TextField} where the user can type different numbers. Only numbers + * will be stored. <br> + * Moreover, the {@link TextField} will turn red if the value currently + * entered if incorrect. + */ + public static class IntegerEditor extends SpreadsheetCellEditor { + + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final TextField tf; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + /** + * Constructor for the IntegerEditor. + * @param view the SpreadsheetView + */ + public IntegerEditor(SpreadsheetView view) { + super(view); + tf = new TextField(); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + /** {@inheritDoc} */ + @Override + public void startEdit(Object value) { + if (value instanceof Integer) { + tf.setText(Integer.toString((Integer) value)); + } else { + tf.setText(null); + } + + tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ + attachEnterEscapeEventHandler(); + + tf.requestFocus(); + tf.selectAll(); + } + + /** {@inheritDoc} */ + @Override + public void end() { + tf.setOnKeyPressed(null); + } + + /** {@inheritDoc} */ + @Override + public TextField getEditor() { + return tf; + } + + /** {@inheritDoc} */ + @Override + public String getControlValue() { + return tf.getText(); + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + private void attachEnterEscapeEventHandler() { + tf.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ENTER) { + try { + if (tf.getText().equals("")) { //$NON-NLS-1$ + endEdit(true); + } else { + Integer.parseInt(tf.getText()); + endEdit(true); + } + } catch (Exception e) { + } + + } else if (t.getCode() == KeyCode.ESCAPE) { + endEdit(false); + } + } + }); + tf.setOnKeyReleased(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + try { + if (tf.getText().equals("")) { //$NON-NLS-1$ + tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ + } else { + Integer.parseInt(tf.getText()); + tf.getStyleClass().removeAll("error"); //$NON-NLS-1$ + } + } catch (Exception e) { + tf.getStyleClass().add("error"); //$NON-NLS-1$ + } + } + }); + } + } + + /** + * + * A {@link SpreadsheetCellEditor} for {@link SpreadsheetCellType.ListType} + * typed cells. It displays a {@link ComboBox} where the user can choose a + * value. + */ + public static class ListEditor<R> extends SpreadsheetCellEditor { + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final List<String> itemList; + private final ComboBox<String> cb; + private String originalValue; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + + /** + * Constructor for the ListEditor. + * @param view The SpreadsheetView + * @param itemList The items to display in the editor. + */ + public ListEditor(SpreadsheetView view, final List<String> itemList) { + super(view); + this.itemList = itemList; + cb = new ComboBox<String>(); + cb.setVisibleRowCount(5); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + + /** {@inheritDoc} */ + @Override + public void startEdit(Object value) { + if (value instanceof String) { + originalValue = value.toString(); + } else { + originalValue = null; + } + ObservableList<String> items = FXCollections.observableList(itemList); + cb.setItems(items); + cb.setValue(originalValue); + + attachEnterEscapeEventHandler(); + cb.show(); + cb.requestFocus(); + } + + /** {@inheritDoc} */ + @Override + public void end() { + cb.setOnKeyPressed(null); + } + + /** {@inheritDoc} */ + @Override + public ComboBox<String> getEditor() { + return cb; + } + + /** {@inheritDoc} */ + @Override + public String getControlValue() { + return cb.getSelectionModel().getSelectedItem(); + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + private void attachEnterEscapeEventHandler() { + + cb.setOnKeyPressed(new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ESCAPE) { + cb.setValue(originalValue); + endEdit(false); + } else if (t.getCode() == KeyCode.ENTER) { + endEdit(true); + } + } + }); + } + } + + /** + * A {@link SpreadsheetCellEditor} for {@link SpreadsheetCellType.DateType} + * typed cells. It displays a {@link DatePicker} where the user can choose a + * date through a visual calendar. + */ + public static class DateEditor extends SpreadsheetCellEditor { + + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final DatePicker datePicker; + private EventHandler<KeyEvent> eh; + private ChangeListener<LocalDate> cl; + /** + * This is needed because "endEdit" will call our "end" method too late + * when pressing enter, so several "endEdit" will be called. So this + * prevent that to happen. + */ + private boolean ending = false; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + /** + * Constructor for the DateEditor. + * @param view the SpreadsheetView + * @param converter A Converter for converting a date to a String. + */ + public DateEditor(SpreadsheetView view, StringConverter<LocalDate> converter) { + super(view); + datePicker = new DatePicker(); + datePicker.setConverter(converter); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + /** {@inheritDoc} */ + @Override + public void startEdit(Object value) { + if (value instanceof LocalDate) { + datePicker.setValue((LocalDate) value); + } + attachEnterEscapeEventHandler(); + datePicker.show(); + datePicker.getEditor().requestFocus(); + } + + /** {@inheritDoc} */ + @Override + public void end() { + if (datePicker.isShowing()) { + datePicker.hide(); + } + datePicker.removeEventFilter(KeyEvent.KEY_PRESSED, eh); + datePicker.valueProperty().removeListener(cl); + } + + /** {@inheritDoc} */ + @Override + public DatePicker getEditor() { + return datePicker; + } + + /** {@inheritDoc} */ + @Override + public String getControlValue() { + return datePicker.getEditor().getText(); + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + + private void attachEnterEscapeEventHandler() { + /** + * We need to add an EventFilter because otherwise the DatePicker + * will block "escape" and "enter". But when "enter" is hit, we need + * to runLater the commit because the value has not yet hit the + * DatePicker itself. + */ + eh = new EventHandler<KeyEvent>() { + @Override + public void handle(KeyEvent t) { + if (t.getCode() == KeyCode.ENTER) { + ending = true; + endEdit(true); + ending = false; + } else if (t.getCode() == KeyCode.ESCAPE) { + endEdit(false); + } + } + }; + + datePicker.addEventFilter(KeyEvent.KEY_PRESSED, eh); + + cl = new ChangeListener<LocalDate>() { + @Override + public void changed(ObservableValue<? extends LocalDate> arg0, LocalDate arg1, LocalDate arg2) { + if (!ending) + endEdit(true); + } + }; + datePicker.valueProperty().addListener(cl); + } + + } +} diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetCellType.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetCellType.java new file mode 100644 index 0000000000000000000000000000000000000000..6412af62d8d59cbc750459b3bff0c6b7471deae9 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetCellType.java @@ -0,0 +1,858 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; + +import javafx.util.StringConverter; +import javafx.util.converter.DefaultStringConverter; +import javafx.util.converter.DoubleStringConverter; +import javafx.util.converter.IntegerStringConverter; + +/** + * When instantiating a {@link SpreadsheetCell}, its SpreadsheetCellType will + * specify which values the cell can accept as user input, and which + * {@link SpreadsheetCellEditor} it will use to receive user input. <br> + * Different static methods are provided in order to give you access to basic + * types, and to create {@link SpreadsheetCell} easily: + * <ul> + * <li><b>String</b>: Accessible with + * {@link SpreadsheetCellType.StringType#createCell(int, int, int, int, String)} + * .</li> + * <li><b>List</b>: Accessible with + * {@link SpreadsheetCellType.ListType#createCell(int, int, int, int, String)}.</li> + * <li><b>Double</b>: Accessible with + * {@link SpreadsheetCellType.DoubleType#createCell(int, int, int, int, Double)} + * .</li> + * <li><b>Integer</b>: Accessible with + * {@link SpreadsheetCellType.IntegerType#createCell(int, int, int, int, Integer)} + * .</li> + * <li><b>Date</b>: Accessible with + * {@link SpreadsheetCellType.DateType#createCell(int, int, int, int, LocalDate)} + * .</li> + * </ul> + * + * <h3>Value verification</h3> You can specify two levels of verification in your + * types. <br> + * <ul> + * <li>The first one is defined by {@link #match(Object)}. It is the first level + * that tells whether or not the given value should be accepted or not. Trying + * to set a String into a Double will return false for example. This method will + * be use by the {@link SpreadsheetView} when trying to set values for example. + * <br> + * </li> + * <li>The second level is defined by {@link #isError(Object)}. This is more + * subtle and allow you to tell whether the given value is coherent or not + * regarding the policy you gave. You can just make a {@link SpreadsheetCell} + * call this method when its value has changed in order to react accordingly if + * the value is in error. (see example below).</li> + * </ul> + * <h3>Converter</h3> You will have to specify a converter for your type. It + * will handle all the conversion between your real value type (Double, Integer, + * LocalDate etc) and its string representation for the cell. <br> + * You can either use a pre-built {@link StringConverter} or our + * {@link StringConverterWithFormat}. This one just add one method ( + * {@link StringConverterWithFormat#toStringFormat(Object, String)} which will + * convert your value with a String format (found in + * {@link SpreadsheetCell#getFormat()}). + * + * <h3>Example</h3> You can create several types which are using the same + * editor. Suppose you want to handle Double values. You will implement the + * {@link #createEditor(SpreadsheetView)} method and use the + * {@link SpreadsheetCellEditor.DoubleEditor}. <br> + * + * Then for each type you will provide your own policy in {@link #match(Object)} + * and in {@link #isError(Object)}, which most of the time will use your + * {@link #converter}. <br> + * + * Here is an example of how to create a {@link StringConverterWithFormat} : + * + * + * + * <pre> + * + * StringConverterWithFormat specialConverter = new StringConverterWithFormat<Double>(new DoubleStringConverter()) { + * @Override + * public String toString(Double item) { + * //We just redirect to the other method. + * return toStringFormat(item, ""); + * } + * + * @Override + * public String toStringFormat(Double item, String format) { + * if (item == null || Double.isNaN(item)) { + * return missingLabel; // For example return something else that an empty cell. + * } else{ + * if (!("").equals(format) && !Double.isNaN(item)) { + * //We format here the value + * return new DecimalFormat(format).format(item); + * } else { + * //We call the DoubleStringConverter that we gave in argument + * return myConverter.toString(item); + * } + * } + * } + * + * @Override + * public Double fromString(String str) { + * if (str == null || str.isEmpty()) { + * return Double.NaN; + * } else { + * try { + * //Just returning the value + * Double myDouble = Double.parseDouble(str); + * return myDouble; + * + * } catch (NumberFormatException e) { + * return myConverter.fromString(str); + * } + * } + * } + * } + * + * </pre> + * + * And then suppose you only want to accept double values between 0 and 100, and + * that a value superior to 10 is abnormal. <br> + * + * <pre> + * @Override + * public boolean isError(Object value) { + * if (value instanceof Double) { + * if ((Double) value > 0 && (Double) value < 10) { + * return false; + * } + * return true; + * } + * return true; + * } + * + * @Override + * public boolean match(Object value) { + * if (value instanceof Double) { + * return true; + * } else { + * try { + * Double convertedValue = converter.fromString(value == null ? null : value.toString()); + * if (convertedValue >= 0 && convertedValue <= 100) + * return true; + * else + * return false; + * } catch (Exception e) { + * return false; + * } + * } + * } + * </pre> + * + * @see SpreadsheetView + * @see SpreadsheetCellEditor + * @see SpreadsheetCell + */ +public abstract class SpreadsheetCellType<T> { + /** An instance of converter from string to cell type. */ + protected StringConverter<T> converter; + + /** + * Default constructor. + */ + public SpreadsheetCellType() { + + } + + /** + * Constructor with the StringConverter directly provided. + * + * @param converter + * The converter to use + */ + public SpreadsheetCellType(StringConverter<T> converter) { + this.converter = converter; + } + + /** + * Creates an editor for this type of cells. + * + * @param view + * the spreadsheet that will own this editor + * @return the editor instance + */ + public abstract SpreadsheetCellEditor createEditor(SpreadsheetView view); + + /** + * Return a string representation of the given item for the + * {@link SpreadsheetView} to display using the inner + * {@link SpreadsheetCellType#converter} and the specified format. + * + * @param object + * @param format + * @return a string representation of the given item. + */ + public String toString(T object, String format) { + return toString(object); + } + + /** + * Return a string representation of the given item for the + * {@link SpreadsheetView} to display using the inner + * {@link SpreadsheetCellType#converter}. + * + * @param object + * @return a string representation of the given item. + */ + public abstract String toString(T object); + + /** + * Verify that the upcoming value can be set to the current cell. This is + * the first level of verification to prevent affecting a text to a double + * or a double to a date. For closer verification, use + * {@link #isError(Object)}. + * + * @param value + * the value to test + * @return true if it matches. + */ + public abstract boolean match(Object value); + + /** + * Returns true if the value is an error regarding the specification of its + * type. + * + * @param value + * @return true if the value is an error. + */ + public boolean isError(Object value) { + return false; + } + + /** + * + * @return true if this SpreadsheetCellType accepts Objects to be dropped on + * the {@link SpreadsheetCell}. Currently only Files can be dropped. If + * accepted, prepare to receive them in {@link #match(java.lang.Object) } + * and {@link #convertValue(java.lang.Object) }. + */ + public boolean acceptDrop() { + return false; + } + + /** + * This method will be called when a commit is happening.<br> + * This method will try to convert the value, be sure to call + * {@link #match(Object)} before to see if this method will succeed. + * + * @param value + * @return null if not valid or the correct value otherwise. + */ + public abstract T convertValue(Object value); + + /** + * The {@link SpreadsheetCell} {@link Object} type instance. + */ + public static final SpreadsheetCellType<Object> OBJECT = new ObjectType(); + + /** + * The {@link SpreadsheetCell} {@link Object} type base class. + */ + public static class ObjectType extends SpreadsheetCellType<Object> { + + public ObjectType() { + this(new StringConverterWithFormat<Object>() { + @Override + public Object fromString(String arg0) { + return arg0; + } + + @Override + public String toString(Object arg0) { + return arg0 == null ? "" : arg0.toString(); //$NON-NLS-1$ + } + }); + } + + public ObjectType(StringConverterWithFormat<Object> converter) { + super(converter); + } + + @Override + public String toString() { + return "object"; //$NON-NLS-1$ + } + + @Override + public boolean match(Object value) { + return true; + } + + /** + * Creates a cell that hold an Object at the specified position, with the + * specified row/column span. + * + * @param row + * row number + * @param column + * column number + * @param rowSpan + * rowSpan (1 is normal) + * @param columnSpan + * ColumnSpan (1 is normal) + * @param value + * the value to display + * @return a {@link SpreadsheetCell} + */ + public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan, + final Object value) { + SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this); + cell.setItem(value); + return cell; + } + + @Override + public SpreadsheetCellEditor createEditor(SpreadsheetView view) { + return new SpreadsheetCellEditor.ObjectEditor(view); + } + + @Override + public Object convertValue(Object value) { + return value; + } + + @Override + public String toString(Object item) { + return converter.toString(item); + } + + }; + + /** + * The {@link SpreadsheetCell} {@link String} type instance. + */ + public static final StringType STRING = new StringType(); + + /** + * The {@link SpreadsheetCell} {@link String} type base class. + */ + public static class StringType extends SpreadsheetCellType<String> { + + public StringType() { + this(new DefaultStringConverter()); + } + + public StringType(StringConverter<String> converter) { + super(converter); + } + + @Override + public String toString() { + return "string"; //$NON-NLS-1$ + } + + @Override + public boolean match(Object value) { + return true; + } + + /** + * Creates a cell that hold a String at the specified position, with the + * specified row/column span. + * + * @param row + * row number + * @param column + * column number + * @param rowSpan + * rowSpan (1 is normal) + * @param columnSpan + * ColumnSpan (1 is normal) + * @param value + * the value to display + * @return a {@link SpreadsheetCell} + */ + public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan, + final String value) { + SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this); + cell.setItem(value); + return cell; + } + + @Override + public SpreadsheetCellEditor createEditor(SpreadsheetView view) { + return new SpreadsheetCellEditor.StringEditor(view); + } + + @Override + public String convertValue(Object value) { + String convertedValue = converter.fromString(value == null ? null : value.toString()); + if (convertedValue == null || convertedValue.equals("")) { //$NON-NLS-1$ + return null; + } + return convertedValue; + } + + @Override + public String toString(String item) { + return converter.toString(item); + } + + }; + + /** + * The {@link SpreadsheetCell} {@link Double} type instance. + */ + public static final DoubleType DOUBLE = new DoubleType(); + + /** + * The {@link SpreadsheetCell} {@link Double} type base class. + */ + public static class DoubleType extends SpreadsheetCellType<Double> { + + public DoubleType() { + + this(new StringConverterWithFormat<Double>(new DoubleStringConverter()) { + @Override + public String toString(Double item) { + return toStringFormat(item, ""); //$NON-NLS-1$ + } + + @Override + public Double fromString(String str) { + if (str == null || str.isEmpty() || "NaN".equals(str)) { //$NON-NLS-1$ + return Double.NaN; + } else { + return myConverter.fromString(str); + } + } + + @Override + public String toStringFormat(Double item, String format) { + try { + if (item == null || Double.isNaN(item)) { + return ""; //$NON-NLS-1$ + } else { + return new DecimalFormat(format).format(item); + } + } catch (Exception ex) { + return myConverter.toString(item); + } + } + }); + } + + public DoubleType(StringConverter<Double> converter) { + super(converter); + } + + @Override + public String toString() { + return "double"; //$NON-NLS-1$ + } + + /** + * Creates a cell that hold a Double at the specified position, with the + * specified row/column span. + * + * @param row + * row number + * @param column + * column number + * @param rowSpan + * rowSpan (1 is normal) + * @param columnSpan + * ColumnSpan (1 is normal) + * @param value + * the value to display + * @return a {@link SpreadsheetCell} + */ + public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan, + final Double value) { + SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this); + cell.setItem(value); + return cell; + } + + @Override + public SpreadsheetCellEditor createEditor(SpreadsheetView view) { + return new SpreadsheetCellEditor.DoubleEditor(view); + } + + @Override + public boolean match(Object value) { + if (value instanceof Double) + return true; + else { + try { + converter.fromString(value == null ? null : value.toString()); + return true; + } catch (Exception e) { + return false; + } + } + } + + @Override + public Double convertValue(Object value) { + if (value instanceof Double) + return (Double) value; + else { + try { + return converter.fromString(value == null ? null : value.toString()); + } catch (Exception e) { + return null; + } + } + } + + @Override + public String toString(Double item) { + return converter.toString(item); + } + + @Override + public String toString(Double item, String format) { + return ((StringConverterWithFormat<Double>) converter).toStringFormat(item, format); + } + }; + + /** + * The {@link SpreadsheetCell} {@link Integer} type instance. + */ + public static final IntegerType INTEGER = new IntegerType(); + + /** + * The {@link SpreadsheetCell} {@link Integer} type base class. + */ + public static class IntegerType extends SpreadsheetCellType<Integer> { + + public IntegerType() { + this(new IntegerStringConverter() { + @Override + public String toString(Integer item) { + if (item == null || Double.isNaN(item)) { + return ""; //$NON-NLS-1$ + } else { + return super.toString(item); + } + } + + @Override + public Integer fromString(String str) { + if (str == null || str.isEmpty() || "NaN".equals(str)) { //$NON-NLS-1$ + return null; + } else { + // We try to integrate Double if possible by truncating + // them + try { + Double temp = Double.parseDouble(str); + return temp.intValue(); + } catch (Exception e) { + return super.fromString(str); + } + } + } + }); + } + + public IntegerType(IntegerStringConverter converter) { + super(converter); + } + + @Override + public String toString() { + return "Integer"; //$NON-NLS-1$ + } + + /** + * Creates a cell that hold a Integer at the specified position, with the + * specified row/column span. + * + * @param row + * row number + * @param column + * column number + * @param rowSpan + * rowSpan (1 is normal) + * @param columnSpan + * ColumnSpan (1 is normal) + * @param value + * the value to display + * @return a {@link SpreadsheetCell} + */ + public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan, + final Integer value) { + SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this); + cell.setItem(value); + return cell; + } + + @Override + public SpreadsheetCellEditor createEditor(SpreadsheetView view) { + return new SpreadsheetCellEditor.IntegerEditor(view); + } + + @Override + public boolean match(Object value) { + if (value instanceof Integer) + return true; + else { + try { + converter.fromString(value == null ? null : value.toString()); + return true; + } catch (Exception e) { + return false; + } + } + } + + @Override + public Integer convertValue(Object value) { + if (value instanceof Integer) + return (Integer) value; + else { + try { + return converter.fromString(value == null ? null : value.toString()); + } catch (Exception e) { + return null; + } + } + } + + @Override + public String toString(Integer item) { + return converter.toString(item); + } + }; + + /** + * Creates a {@link ListType}. + * + * @param items + * the list items + * @return the instance + */ + public static final ListType LIST(final List<String> items) { + return new ListType(items); + } + + /** + * The {@link SpreadsheetCell} {@link List} type base class. + */ + public static class ListType extends SpreadsheetCellType<String> { + protected final List<String> items; + + public ListType(final List<String> items) { + super(new DefaultStringConverter() { + @Override + public String fromString(String str) { + if (str != null && items.contains(str)) { + return str; + } else { + return null; + } + } + + }); + this.items = items; + } + + @Override + public String toString() { + return "list"; //$NON-NLS-1$ + } + + /** + * Creates a cell that hold a String at the specified position, with the + * specified row/column span. + * + * @param row + * row number + * @param column + * column number + * @param rowSpan + * rowSpan (1 is normal) + * @param columnSpan + * ColumnSpan (1 is normal) + * @param value + * the value to display + * @return a {@link SpreadsheetCell} + */ + public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan, + String value) { + SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this); + if (items != null && items.size() > 0) { + if (value != null && items.contains(value)) { + cell.setItem(value); + } else { + cell.setItem(items.get(0)); + } + } + return cell; + } + + @Override + public SpreadsheetCellEditor createEditor(SpreadsheetView view) { + return new SpreadsheetCellEditor.ListEditor<>(view, items); + } + + @Override + public boolean match(Object value) { + if (value instanceof String && items.contains(value.toString())) + return true; + else + return items.contains(value == null ? null : value.toString()); + } + + @Override + public String convertValue(Object value) { + return converter.fromString(value == null ? null : value.toString()); + } + + @Override + public String toString(String item) { + return converter.toString(item); + } + } + + /** + * The {@link SpreadsheetCell} {@link LocalDate} type instance. + */ + public static final DateType DATE = new DateType(); + + /** + * The {@link SpreadsheetCell} {@link LocalDate} type base class. + */ + public static class DateType extends SpreadsheetCellType<LocalDate> { + + /** + * Creates a new DateType. + */ + public DateType() { + this(new StringConverterWithFormat<LocalDate>() { + @Override + public String toString(LocalDate item) { + return toStringFormat(item, ""); //$NON-NLS-1$ + } + + @Override + public LocalDate fromString(String str) { + try { + return LocalDate.parse(str); + } catch (Exception e) { + return null; + } + } + + @Override + public String toStringFormat(LocalDate item, String format) { + if (("").equals(format) && item != null) { //$NON-NLS-1$ + return item.toString(); + } else if (item != null) { + return item.format(DateTimeFormatter.ofPattern(format)); + } else { + return ""; //$NON-NLS-1$ + } + } + }); + } + + public DateType(StringConverter<LocalDate> converter) { + super(converter); + } + + @Override + public String toString() { + return "date"; //$NON-NLS-1$ + } + + /** + * Creates a cell that hold a LocalDate at the specified position, with the + * specified row/column span. + * + * @param row + * row number + * @param column + * column number + * @param rowSpan + * rowSpan (1 is normal) + * @param columnSpan + * ColumnSpan (1 is normal) + * @param value + * the value to display + * @return a {@link SpreadsheetCell} + */ + public SpreadsheetCell createCell(final int row, final int column, final int rowSpan, final int columnSpan, + final LocalDate value) { + SpreadsheetCell cell = new SpreadsheetCellBase(row, column, rowSpan, columnSpan, this); + cell.setItem(value); + return cell; + } + + @Override + public SpreadsheetCellEditor createEditor(SpreadsheetView view) { + return new SpreadsheetCellEditor.DateEditor(view, converter); + } + + @Override + public boolean match(Object value) { + if (value instanceof LocalDate) + return true; + else { + try { + LocalDate temp = converter.fromString(value == null ? null : value.toString()); + return temp != null; + } catch (Exception e) { + return false; + } + } + } + + @Override + public LocalDate convertValue(Object value) { + if (value instanceof LocalDate) + return (LocalDate) value; + else { + try { + return converter.fromString(value == null ? null : value.toString()); + } catch (Exception e) { + return null; + } + } + } + + @Override + public String toString(LocalDate item) { + return converter.toString(item); + } + + @Override + public String toString(LocalDate item, String format) { + return ((StringConverterWithFormat<LocalDate>) converter).toStringFormat(item, format); + } + } +} \ No newline at end of file diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetColumn.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..bf45a1ed686c7d6bed4b0a08b861beef77b91664 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetColumn.java @@ -0,0 +1,372 @@ +/** + * Copyright (c) 2013, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; +import impl.org.controlsfx.spreadsheet.CellView; +import java.util.List; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.stage.WindowEvent; +import org.controlsfx.tools.Utils; + +/** + * A {@link SpreadsheetView} is made up of a number of {@link SpreadsheetColumn} + * instances. + * + * <h3>Configuration</h3> SpreadsheetColumns are instantiated by the + * {@link SpreadsheetView} itself, so there is no public constructor for this + * class. To access the available columns, you need to call + * {@link SpreadsheetView#getColumns()}. + * + * <p> + * SpreadsheetColumn gives you the ability to modify some aspects of the column, + * for example the {@link #setPrefWidth(double) width} or + * {@link #setResizable(boolean) resizability} of the column. + * + * <p> + * You have the ability to fix this column at the left of the SpreadsheetView by + * calling {@link #setFixed(boolean)}. But you are strongly advised to check if + * it is possible with {@link #isColumnFixable()} before calling + * {@link #setFixed(boolean)}. Take a look at the {@link SpreadsheetView} + * description to understand the fixing constraints. + * + * <p> + * If the column can be fixed, a {@link ContextMenu} will appear if the user right-clicks on it. + * If not, nothing will appear and the user will not have the possibility to fix it. + * + * <h3>Screenshot</h3> + * The column <b>A</b> is fixed and is covering column <b>B</b> and partially + * column <b>C</b>. The context menu is being shown and offers the possibility + * to unfix the column. + * + * <br> + * <br> + * <center><img src="fixedColumn.png" alt="Screenshot of SpreadsheetColumn"></center> + * + * @see SpreadsheetView + */ +public final class SpreadsheetColumn { + + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + private final SpreadsheetView spreadsheetView; + final TableColumn<ObservableList<SpreadsheetCell>, SpreadsheetCell> column; + private final boolean canFix; + private final Integer indexColumn; + private MenuItem fixItem; + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + /** + * Creates a new SpreadsheetColumn. + * + * @param column + * @param spreadsheetView + * @param indexColumn + */ + SpreadsheetColumn(final TableColumn<ObservableList<SpreadsheetCell>, SpreadsheetCell> column, + final SpreadsheetView spreadsheetView, final Integer indexColumn, Grid grid) { + this.spreadsheetView = spreadsheetView; + this.column = column; + column.setMinWidth(0); + this.indexColumn = indexColumn; + canFix = initCanFix(grid); + + // The contextMenu creation must be on the JFX thread + CellView.getValue(() -> { + column.setContextMenu(getColumnContextMenu()); + }); + + // When changing frozen fixed columns, we need to update the ContextMenu. + spreadsheetView.fixingColumnsAllowedProperty().addListener(new ChangeListener<Boolean>() { + + @Override + public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { + CellView.getValue(() -> { + column.setContextMenu(getColumnContextMenu()); + }); + } + }); + + // When ColumnsHeaders are changing, we update the text + grid.getColumnHeaders().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable arg0) { + List<String> columnsHeader = spreadsheetView.getGrid().getColumnHeaders(); + if (columnsHeader.size() <= indexColumn) { + setText(Utils.getExcelLetterFromNumber(indexColumn)); + } else if (!columnsHeader.get(indexColumn).equals(getText())) { + setText(columnsHeader.get(indexColumn)); + } + } + }); + + // When changing rows, we re-calculate if this columns can be fixed. + grid.getRows().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable arg0) { + initCanFix(grid); + } + }); + } + + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + + /** + * Return whether this column is fixed or not. + * + * @return true if this column is fixed. + */ + public boolean isFixed() { + return spreadsheetView.getFixedColumns().contains(this); + } + + /** + * Fix this column to the left if possible, although it is recommended that + * you call {@link #isColumnFixable()} before trying to fix a column. + * + * If you want to fix several columns (because of a span for example), add + * all the columns directly in {@link SpreadsheetView#getFixedColumns() }. + * Always use {@link SpreadsheetView#areSpreadsheetColumnsFixable(java.util.List) + * } before. + * + * @param fixed + */ + public void setFixed(boolean fixed) { + if (fixed) { + spreadsheetView.getFixedColumns().add(this); + } else { + spreadsheetView.getFixedColumns().removeAll(this); + } + } + + /** + * Set the width of this column. + * + * @param width + */ + public void setPrefWidth(double width) { + width = Math.ceil(width); + if (column.getPrefWidth() == width && column.getWidth() != width) { + column.impl_setWidth(width); + } else { + column.setPrefWidth(width); + } + spreadsheetView.columnWidthSet(indexColumn); + } + + /** + * Return the actual width of the column. + * + * @return the actual width of the column + */ + public double getWidth() { + return column.getWidth(); + } + + /** + * Return the Property related to the actual width of the column. + * + * @return + */ + public final ReadOnlyDoubleProperty widthProperty() { + return column.widthProperty(); + } + + /** + * Set the minimum width for this SpreadsheetColumn. + * + * @param value + */ + public final void setMinWidth(double value) { + column.setMinWidth(value); + } + + /** + * Return the minimum width for this SpreadsheetColumn. + * + * @return + */ + public final double getMinWidth() { + return column.getMinWidth(); + } + + /** + * Return the Property related to the minimum width of this + * SpreadsheetColumn. + * + * @return + */ + public final DoubleProperty minWidthProperty() { + return column.minWidthProperty(); + } + + /** + * Return the Property related to the maximum width of this + * SpreadsheetColumn. + * + * @return + */ + public final DoubleProperty maxWidthProperty() { + return column.maxWidthProperty(); + } + + /** + * Set the maximum width for this SpreadsheetColumn. + * + * @param value + */ + public final void setMaxWidth(double value) { + column.setMaxWidth(value); + } + + /** + * Return the maximum width for this SpreadsheetColumn. + * + * @return + */ + public final double getMaxWidth() { + return column.getMaxWidth(); + } + /** + * If this column can be resized by the user + * + * @param b + */ + public void setResizable(boolean b) { + column.setResizable(b); + } + + /** + * If the column is resizable, it will compute the optimum width for all the + * visible cells to be visible. + */ + public void fitColumn() { + if (column.isResizable() && spreadsheetView.getCellsViewSkin() != null) { + spreadsheetView.getCellsViewSkin().resize(column, 100); + } + } + + /** + * Indicate whether this column can be fixed or not. Call that method before + * calling {@link #setFixed(boolean)} or adding an item to + * {@link SpreadsheetView#getFixedColumns()}. + * + * A column cannot be fixed alone if any cell inside the column has a column + * span superior to one. + * + * @return true if this column is fixable. + */ + public boolean isColumnFixable() { + return canFix && spreadsheetView.isFixingColumnsAllowed(); + } + + /*************************************************************************** + * * Private Methods * * + **************************************************************************/ + private void setText(String text) { + column.setText(text); + } + + private String getText() { + return column.getText(); + } + + /** + * Generate a context Menu in order to fix/unfix some column It is shown + * when right-clicking on the column header + * + * @return a context menu. + */ + private ContextMenu getColumnContextMenu() { + if (isColumnFixable()) { + final ContextMenu contextMenu = new ContextMenu(); + + this.fixItem = new MenuItem(localize(asKey("spreadsheet.column.menu.fix"))); //$NON-NLS-1$ + contextMenu.setOnShowing(new EventHandler<WindowEvent>() { + + @Override + public void handle(WindowEvent event) { + if (!isFixed()) { + fixItem.setText(localize(asKey("spreadsheet.column.menu.fix"))); //$NON-NLS-1$ + } else { + fixItem.setText(localize(asKey("spreadsheet.column.menu.unfix"))); //$NON-NLS-1$ + } + } + }); + fixItem.setGraphic(new ImageView(new Image(getClass().getResourceAsStream("pinSpreadsheetView.png")))); //$NON-NLS-1$ + fixItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent arg0) { + if (!isFixed()) { + setFixed(true); + } else { + setFixed(false); + } + } + }); + contextMenu.getItems().addAll(fixItem); + + return contextMenu; + } else { + return new ContextMenu(); + } + } + + /** + * Verify that you can fix this column. + * + * @return if it's fixable. + */ + private boolean initCanFix(Grid grid) { + for (ObservableList<SpreadsheetCell> row : grid.getRows()) { + int columnSpan = row.get(indexColumn).getColumnSpan(); + if (columnSpan > 1) { + return false; + } + } + return true; + } +} diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetView.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetView.java new file mode 100644 index 0000000000000000000000000000000000000000..fef76586165609cde6644391d22e0cd27d2dafd1 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetView.java @@ -0,0 +1,1928 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; +import impl.org.controlsfx.spreadsheet.CellView; +import impl.org.controlsfx.spreadsheet.FocusModelListener; +import impl.org.controlsfx.spreadsheet.GridViewSkin; +import impl.org.controlsfx.spreadsheet.RectangleSelection.GridRange; +import impl.org.controlsfx.spreadsheet.RectangleSelection.SelectionRange; +import impl.org.controlsfx.spreadsheet.SpreadsheetGridView; +import impl.org.controlsfx.spreadsheet.SpreadsheetHandle; +import impl.org.controlsfx.spreadsheet.TableViewSpanSelectionModel; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WeakChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.event.EventType; +import javafx.event.WeakEventHandler; +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Control; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.ScrollBar; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.Skin; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import javafx.stage.WindowEvent; +import javafx.util.Pair; + +import org.controlsfx.control.PropertySheet.Item; +import org.controlsfx.tools.Utils; + +/** + * The SpreadsheetView is a control similar to the JavaFX {@link TableView} + * control but with different functionalities and use cases. The aim is to have + * a powerful grid where data can be written and retrieved. + * + * <h3>Features</h3> + * <ul> + * <li>Cells can span in row and in column.</li> + * <li>Rows can be fixed to the top of the {@link SpreadsheetView} so that they + * are always visible on screen.</li> + * <li>Columns can be fixed to the left of the {@link SpreadsheetView} so that + * they are always visible on screen.</li> + * <li>A row header can be switched on in order to display the row number.</li> + * <li>Rows can be resized just like columns with click & drag.</li> + * <li>Both row and column header can be visible or invisible.</li> + * <li>Selection of several cells can be made with a click and drag.</li> + * <li>A copy/paste context menu is accessible with a right-click. The usual + * shortcuts are also working.</li> + * <li>{@link Picker} can be placed above column header or to the side of the + * row header.</li> + * </ul> + * + * <br> + * + * <h3>Fixing Rows and Columns</h3> + * <br> + * You can fix some rows and some columns by right-clicking on their header. A + * context menu will appear if it's possible to fix them. When fixed, the label + * header will then be in italic and the background will turn to dark grey. + * <br> + * You have also the possibility to fix them manually by adding and removing + * items from {@link #getFixedRows()} and {@link #getFixedColumns()}. But you + * are strongly advised to check if it's possible to do so with + * {@link SpreadsheetColumn#isColumnFixable()} for the fixed columns and with + * {@link #isRowFixable(int)} for the fixed rows. + * <br> + * + * A set of rows cannot be fixed if any cell inside these rows has a row span + * superior to the number of fixed rows. Likewise, a set of columns cannot be + * fixed if any cell inside these columns has a column span superior to the + * number of fixed columns. + * + * <br><br> + * If you want to fix several rows or columns together, and they have a span + * inside, you can call {@link #areRowsFixable(java.util.List) } or {@link #areSpreadsheetColumnsFixable(java.util.List) + * } + * to verify if you can fix them. Be sure to add them all in once otherwise the + * system will detect that a span is going out of bounds and will throw an + * exception. + * + * Calling those methods prior + * every move will ensure that no exception will be thrown. + * <br><br> + * You have also the possibility to deactivate these possibilities. For example, + * you force some row/column to be fixed and then the user cannot change the + * settings. + * <br> + * + * <h3>Headers</h3> + * <br> + * You can also access and toggle header's visibility by using the methods + * provided like {@link #setShowRowHeader(boolean) } or {@link #setShowColumnHeader(boolean) + * }. + * + * <br> + * Users can double-click on a column header will resize the column to the best + * size in order to fully see each cell in it. Same rule apply for row header. + * Also note that double-clicking on the little space between two row or two + * columns (when resizable) will also work just like Excel. + * + * <h3>Pickers</h3> + * <br> + * + * You can show some little images next to the headers. They will appear on the + * left of the VerticalHeader and on top on the HorizontalHeader. They are called + * "picker" because they were used originally for picking a row or a column to + * insert in the SpreadsheetView. + * <br> + * But you can do anything you want with it. Simply put a row or a column index + * in {@link #getRowPickers() } and {@link #getColumnPickers() } along with an + * instance of {@link Picker}. You can override the {@link Picker#onClick() } + * method in order to react when the user click on the picker. + * <br> + * The pickers will appear on the top of the column's header and on the left of + * the row's header. + * <br> + * + * <h3>Copy pasting</h3> You can copy any cell you want and paste it elsewhere. + * Be aware that only the value inside will be pasted, not the style nor the + * type. Thus the value you're trying to paste must be compatible with the + * {@link SpreadsheetCellType} of the receiving cell. Pasting a Double into a + * String will work but the reverse operation will not. + * <br> + * See {@link SpreadsheetCellType} <i>Value Verification</i> documentation for more + * information. + * <br> + * A unique cell or a selection can be copied and pasted. + * + * <br> + * <br> + * <h3>Code Samples</h3> Just like the {@link TableView}, you instantiate the + * underlying model, a {@link Grid}. You will create some rows filled with {@link SpreadsheetCell}. + * + * <br> + * <br> + * + * <pre> + * int rowCount = 15; + * int columnCount = 10; + * GridBase grid = new GridBase(rowCount, columnCount); + * + * ObservableList<ObservableList<SpreadsheetCell>> rows = FXCollections.observableArrayList(); + * for (int row = 0; row < grid.getRowCount(); ++row) { + * final ObservableList<SpreadsheetCell> list = FXCollections.observableArrayList(); + * for (int column = 0; column < grid.getColumnCount(); ++column) { + * list.add(SpreadsheetCellType.STRING.createCell(row, column, 1, 1,"value")); + * } + * rows.add(list); + * } + * grid.setRows(rows); + * + * SpreadsheetView spv = new SpreadsheetView(grid); + * + * </pre> + * + * At that moment you can span some of the cells with the convenient method + * provided by the grid. Then you just need to instantiate the SpreadsheetView. <br> + * <h3>Visual:</h3> <center><img src="spreadsheetView.png" alt="Screenshot of SpreadsheetView"></center> + * + * @see SpreadsheetCell + * @see SpreadsheetCellBase + * @see SpreadsheetColumn + * @see Grid + * @see GridBase + * @see Picker + */ +public class SpreadsheetView extends Control{ + + /*************************************************************************** + * * Static Fields * * + **************************************************************************/ + + /** + * The SpanType describes in which state each cell can be. When a spanning + * is occurring, one cell is becoming larger and the others are becoming + * invisible. Thus, that particular cell is masking the others. <br> + * <br> + * But the SpanType cannot be known in advance because it's evolving for + * each cell during the lifetime of the {@link SpreadsheetView}. Suppose you + * have a cell spanning in row, the first one is in a ROW_VISIBLE state, and + * all the other below are in a ROW_SPAN_INVISIBLE state. But if the user is + * scrolling down, the first will go out of sight. At that moment, the + * second cell is switching from ROW_SPAN_INVISIBLE state to ROW_VISIBLE + * state. <br> + * <br> + * + * <center><img src="spanType.png" alt="Screenshot of SpreadsheetView.SpanType"></center> + * Refer to {@link SpreadsheetView} for more information. + */ + public static enum SpanType { + + /** + * Visible cell, can be a unique cell (no span) or the first one inside + * a column spanning cell. + */ + NORMAL_CELL, + + /** + * Invisible cell because a cell in a NORMAL_CELL state on the left is + * covering it. + */ + COLUMN_SPAN_INVISIBLE, + + /** + * Invisible cell because a cell in a ROW_VISIBLE state on the top is + * covering it. + */ + ROW_SPAN_INVISIBLE, + + /** Visible Cell but has some cells below in a ROW_SPAN_INVISIBLE state. */ + ROW_VISIBLE, + + /** + * Invisible cell situated in diagonal of a cell in a ROW_VISIBLE state. + */ + BOTH_INVISIBLE; + } + + /** + * Default width of the VerticalHeader. + */ + private static final double DEFAULT_ROW_HEADER_WIDTH = 30.0; + /*************************************************************************** + * * Private Fields * * + **************************************************************************/ + + protected final SpreadsheetGridView cellsView;// The main cell container. + private SimpleObjectProperty<Grid> gridProperty = new SimpleObjectProperty<>(); + private DataFormat fmt; + + private final ObservableList<Integer> fixedRows = FXCollections.observableArrayList(); + private final ObservableList<SpreadsheetColumn> fixedColumns = FXCollections.observableArrayList(); + + private final BooleanProperty fixingRowsAllowedProperty = new SimpleBooleanProperty(true); + private final BooleanProperty fixingColumnsAllowedProperty = new SimpleBooleanProperty(true); + + private final BooleanProperty showColumnHeader = new SimpleBooleanProperty(true, "showColumnHeader", true); //$NON-NLS-1$ + private final BooleanProperty showRowHeader = new SimpleBooleanProperty(true, "showRowHeader", true); //$NON-NLS-1$ + + private BitSet rowFix; // Compute if we can fix the rows or not. + + private final ObservableMap<Integer, Picker> rowPickers = FXCollections.observableHashMap(); + + private final ObservableMap<Integer, Picker> columnPickers = FXCollections.observableHashMap(); + + // Properties needed by the SpreadsheetView and managed by the skin (source + // is the VirtualFlow) + private ObservableList<SpreadsheetColumn> columns = FXCollections.observableArrayList(); + private Map<SpreadsheetCellType<?>, SpreadsheetCellEditor> editors = new IdentityHashMap<>(); + private final SpreadsheetViewSelectionModel selectionModel; + + /** + * The vertical header width, just for the Label, not the Pickers. + */ + private final DoubleProperty rowHeaderWidth = new SimpleDoubleProperty(DEFAULT_ROW_HEADER_WIDTH); + + /** + * Since the default with applied to TableColumn is 80. If a user sets a + * width of 80, the column will be detected as having the default with and + * therefore will be requested to be autosized. In order to prevent that, we + * must detect which columns has been specifically set and which not. With + * that BitSet, we are able to make the difference between a "default" 80 + * width applied by the system, and a 80 width applid by a user. + */ + private final BitSet columnWidthSet = new BitSet(); + // The handle that bridges with implementation. + final SpreadsheetHandle handle = new SpreadsheetHandle() { + + @Override + protected SpreadsheetView getView() { + return SpreadsheetView.this; + } + + @Override + protected GridViewSkin getCellsViewSkin() { + return SpreadsheetView.this.getCellsViewSkin(); + } + + @Override + protected SpreadsheetGridView getGridView() { + return SpreadsheetView.this.getCellsView(); + } + + @Override + protected boolean isColumnWidthSet(int indexColumn) { + return columnWidthSet.get(indexColumn); + } + }; + + /** + * @return the inner table view skin + */ + public final GridViewSkin getCellsViewSkin() { + return (GridViewSkin) (cellsView.getSkin()); + } + + /** + * @return the inner table view + */ + final SpreadsheetGridView getCellsView() { + return cellsView; + } + + /** + * Used by {@link SpreadsheetColumn} internally in order to specify if a + * column width has been set by the user. + * + * @param indexColumn + */ + void columnWidthSet(int indexColumn) { + columnWidthSet.set(indexColumn); + } + + /*************************************************************************** + * * Constructor * * + **************************************************************************/ + + /** + * This constructor will generate sample Grid with 100 rows and 15 columns. + * All cells are typed as String (see {@link SpreadsheetCellType#STRING}). + */ + public SpreadsheetView(){ + this(getSampleGrid()); + for(SpreadsheetColumn column: getColumns()){ + column.setPrefWidth(100); + } + } + + /** + * Creates a SpreadsheetView control with the {@link Grid} specified. + * + * @param grid The Grid that contains the items to be rendered + */ + public SpreadsheetView(final Grid grid) { + super(); + //We want to recompute the rectangleHeight when a fixedRow is resized. + addEventHandler(RowHeightEvent.ROW_HEIGHT_CHANGE, (RowHeightEvent event) -> { + if(getFixedRows().contains(event.getRow()) && getCellsViewSkin() != null){ + getCellsViewSkin().computeFixedRowHeight(); + } + }); + getStyleClass().add("SpreadsheetView"); //$NON-NLS-1$ + // anonymous skin + setSkin(new Skin<SpreadsheetView>() { + @Override + public Node getNode() { + return SpreadsheetView.this.getCellsView(); + } + + @Override + public SpreadsheetView getSkinnable() { + return SpreadsheetView.this; + } + + @Override + public void dispose() { + // no-op + } + }); + + this.cellsView = new SpreadsheetGridView(handle); + getChildren().add(cellsView); + + /** + * Add a listener to the selection model in order to edit the spanned + * cells when clicked + */ + TableViewSpanSelectionModel tableViewSpanSelectionModel = new TableViewSpanSelectionModel(this,cellsView); + cellsView.setSelectionModel(tableViewSpanSelectionModel); + tableViewSpanSelectionModel.setCellSelectionEnabled(true); + tableViewSpanSelectionModel.setSelectionMode(SelectionMode.MULTIPLE); + selectionModel = new SpreadsheetViewSelectionModel(this, tableViewSpanSelectionModel); + + /** + * Set the focus model to track keyboard change and redirect focus on + * spanned cells + */ + // We add a listener on the focus model in order to catch when we are on + // a hidden cell + cellsView.getFocusModel().focusedCellProperty() + .addListener((ChangeListener<TablePosition>) (ChangeListener<?>) new FocusModelListener(this,cellsView)); + + /** + * Keyboard action, maybe use an accelerator + */ + cellsView.setOnKeyPressed(keyPressedHandler); + + /** + * ContextMenu handling. + */ + this.contextMenuProperty().addListener(new WeakChangeListener<>(contextMenuChangeListener)); + // The contextMenu creation must be on the JFX thread + CellView.getValue(() -> { + setContextMenu(getSpreadsheetViewContextMenu()); + }); + + setGrid(grid); + setEditable(true); + + // Listeners & handlers + fixedRows.addListener(fixedRowsListener); + fixedColumns.addListener(fixedColumnsListener); + } + /*************************************************************************** + * * Public Methods * * + **************************************************************************/ + + /** + * Set a new Grid for the SpreadsheetView. This will be called by default by + * {@link #SpreadsheetView(Grid)}. So this is useful when you want to + * refresh your SpreadsheetView with a new model. This will keep the state + * of your SpreadsheetView (position of the bar, number of fixedRows etc). + * + * @param grid the new Grid + */ + public final void setGrid(Grid grid) { + if(grid == null){ + return; + } + // Reactivate that after +// verifyGrid(grid); + gridProperty.set(grid); + initRowFix(grid); + + /** + * We need to verify that the previous fixedRows are still compatible + * with our new model + */ + + List<Integer> newFixedRows = new ArrayList<>(); + for (Integer rowFixed : getFixedRows()) { + if (isRowFixable(rowFixed)) { + newFixedRows.add(rowFixed); + } + } + getFixedRows().setAll(newFixedRows); + + /** + * We need to store the index of the fixedColumns and clear then because + * we will keep reference to SpreadsheetColumn that no longer exist. + */ + List<Integer> columnsFixed = new ArrayList<>(); + for (SpreadsheetColumn column : getFixedColumns()) { + columnsFixed.add(getColumns().indexOf(column)); + } + getFixedColumns().clear(); + + /** + * We try to save the width of the column as we save the height of our rows so that we preserve the state. + */ + List<Double> widthColumns = new ArrayList<>(); + for (SpreadsheetColumn column : columns) { + widthColumns.add(column.getWidth()); + } + //We need to update the focused cell afterwards + Pair<Integer, Integer> focusedPair = null; + TablePosition focusedCell = cellsView.getFocusModel().getFocusedCell(); + if (focusedCell != null && focusedCell.getRow() != -1 && focusedCell.getColumn() != -1) { + focusedPair = new Pair(focusedCell.getRow(), focusedCell.getColumn()); + } + + final Pair<Integer, Integer> finalPair = focusedPair; + + if (grid.getRows() != null) { + final ObservableList<ObservableList<SpreadsheetCell>> observableRows = FXCollections + .observableArrayList(grid.getRows()); + cellsView.getItems().clear(); + cellsView.setItems(observableRows); + + final int columnCount = grid.getColumnCount(); + columns.clear(); + for (int columnIndex = 0; columnIndex < columnCount; ++columnIndex) { + final SpreadsheetColumn spreadsheetColumn = new SpreadsheetColumn(getTableColumn(grid, columnIndex), this, columnIndex, grid); + if(widthColumns.size() > columnIndex){ + spreadsheetColumn.setPrefWidth(widthColumns.get(columnIndex)); + } + columns.add(spreadsheetColumn); + // We verify if this column was fixed before and try to re-fix + // it. + if (columnsFixed.contains((Integer) columnIndex) && spreadsheetColumn.isColumnFixable()) { + spreadsheetColumn.setFixed(true); + } + } + } + + List<Pair<Integer, Integer>> selectedCells = new ArrayList<>(); + for (TablePosition position : getSelectionModel().getSelectedCells()) { + selectedCells.add(new Pair<>(position.getRow(), position.getColumn())); + } + + + /** + * Since the TableView is added to the sceneGraph, it's not possible to + * modify the columns in another thread. We normally should call + * Platform.runLater() and exit. But in this particular case, we need to + * add the tableColumn right now. So that when we exit this "setGrid" + * method, we are sure we can manipulate all the elements. + * + * We also try to be smart here when we already have some columns in + * order to re-use them and minimize the time used to add/remove + * columns. + */ + Runnable runnable = () -> { + if (cellsView.getColumns().size() > grid.getColumnCount()) { + cellsView.getColumns().remove(grid.getColumnCount(), cellsView.getColumns().size()); + } else if (cellsView.getColumns().size() < grid.getColumnCount()) { + for (int i = cellsView.getColumns().size(); i < grid.getColumnCount(); ++i) { + cellsView.getColumns().add(columns.get(i).column); + } + } + ((TableViewSpanSelectionModel) cellsView.getSelectionModel()).verifySelectedCells(selectedCells); + //Just like the selected cell we update the focused cell. + if(finalPair != null && finalPair.getKey() < getGrid().getRowCount() && finalPair.getValue() < getGrid().getColumnCount()){ + cellsView.getFocusModel().focus(finalPair.getKey(), cellsView.getColumns().get(finalPair.getValue())); + } + }; + + if (Platform.isFxApplicationThread()) { + runnable.run(); + } else { + try { + FutureTask future = new FutureTask(runnable, null); + Platform.runLater(future); + future.get(); + } catch (InterruptedException | ExecutionException ex) { + Logger.getLogger(SpreadsheetView.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + /** + * Return a {@link TablePosition} of cell being currently edited. + * + * @return a {@link TablePosition} of cell being currently edited. + */ + public TablePosition<ObservableList<SpreadsheetCell>, ?> getEditingCell() { + return cellsView.getEditingCell(); + } + + /** + * Represents the current cell being edited, or null if there is no cell + * being edited. + * + * @return the current cell being edited, or null if there is no cell being + * edited. + */ + public ReadOnlyObjectProperty<TablePosition<ObservableList<SpreadsheetCell>, ?>> editingCellProperty() { + return cellsView.editingCellProperty(); + } + + /** + * Return an ObservableList of the {@link SpreadsheetColumn} used. This list + * is filled automatically by the SpreadsheetView. Adding and removing + * columns should be done in the model {@link Grid}. + * + * @return An ObservableList of the {@link SpreadsheetColumn} + */ + public final ObservableList<SpreadsheetColumn> getColumns() { + return columns; + } + + /** + * Return the model Grid used by the SpreadsheetView + * + * @return the model Grid used by the SpreadsheetView + */ + public final Grid getGrid() { + return gridProperty.get(); + } + + /** + * Return a {@link ReadOnlyObjectProperty} containing the current Grid + * used in the SpreadsheetView. + * @return a {@link ReadOnlyObjectProperty}. + */ + public final ReadOnlyObjectProperty<Grid> gridProperty() { + return gridProperty; + } + + /** + * You can fix or unfix a row by modifying this list. Call + * {@link #isRowFixable(int)} before trying to fix a row. See + * {@link SpreadsheetView} description for information. + * + * @return an ObservableList of integer representing the fixedRows. + */ + public ObservableList<Integer> getFixedRows() { + return fixedRows; + } + + /** + * Indicate whether a row can be fixed or not. Call that method before + * adding an item with {@link #getFixedRows()} . + * + * A row cannot be fixed alone if any cell inside the row has a row span + * superior to one. + * + * @param row + * @return true if the row can be fixed. + */ + public boolean isRowFixable(int row) { + return row >= 0 && row < rowFix.size() && isFixingRowsAllowed() ? rowFix.get(row) : false; + } + + /** + * Indicates whether a List of rows can be fixed or not. + * + * A set of rows cannot be fixed if any cell inside these rows has a row + * span superior to the number of fixed rows. + * + * @param list + * @return true if the List of row can be fixed together. + */ + public boolean areRowsFixable(List<? extends Integer> list) { + if(list == null || list.isEmpty() || !isFixingRowsAllowed()){ + return false; + } + final Grid grid = getGrid(); + final int rowCount = grid.getRowCount(); + final ObservableList<ObservableList<SpreadsheetCell>> rows = grid.getRows(); + for (Integer row : list) { + if (row == null || row < 0 || row >= rowCount) { + return false; + } + //If this row is not fixable, we need to identify the maximum span + if (!isRowFixable(row)) { + int maxSpan = 1; + List<SpreadsheetCell> gridRow = rows.get(row); + for (SpreadsheetCell cell : gridRow) { + //If the original row is not within this range, there is not need to look deeper. + if (!list.contains(cell.getRow())) { + return false; + } + //We only want to consider the original cell. + if (cell.getRowSpan() > maxSpan && cell.getRow() == row) { + maxSpan = cell.getRowSpan(); + } + } + //Then we need to verify that all rows within that span are fixed. + int count = row + maxSpan - 1; + for (int index = row + 1; index <= count; ++index) { + if (!list.contains(index)) { + return false; + } + } + } + } + return true; + } + + /** + * Return whether change to Fixed rows are allowed. + * + * @return whether change to Fixed rows are allowed. + */ + public boolean isFixingRowsAllowed() { + return fixingRowsAllowedProperty.get(); + } + + /** + * If set to true, user will be allowed to fix and unfix the rows. + * + * @param b + */ + public void setFixingRowsAllowed(boolean b) { + fixingRowsAllowedProperty.set(b); + } + + /** + * Return the Boolean property associated with the allowance of fixing or + * unfixing some rows. + * + * @return the Boolean property associated with the allowance of fixing or + * unfixing some rows. + */ + public ReadOnlyBooleanProperty fixingRowsAllowedProperty() { + return fixingRowsAllowedProperty; + } + + /** + * You can fix or unfix a column by modifying this list. Call + * {@link SpreadsheetColumn#isColumnFixable()} on the column before adding + * an item. + * + * @return an ObservableList of the fixed columns. + */ + public ObservableList<SpreadsheetColumn> getFixedColumns() { + return fixedColumns; + } + + /** + * Indicate whether this column can be fixed or not. If you have a + * {@link SpreadsheetColumn}, call + * {@link SpreadsheetColumn#isColumnFixable()} on it directly. Call that + * method before adding an item with {@link #getFixedColumns()} . + * + * @param columnIndex + * @return true if the column if fixable + */ + public boolean isColumnFixable(int columnIndex) { + return columnIndex >= 0 && columnIndex < getColumns().size() && isFixingColumnsAllowed() + ? getColumns().get(columnIndex).isColumnFixable() : false; + } + + /** + * Indicates whether a List of {@link SpreadsheetColumn} can be fixed or + * not. + * + * A set of columns cannot be fixed if any cell inside these columns has a + * column span superior to the number of fixed columns. + * + * @param list + * @return true if the List of columns can be fixed together. + */ + public boolean areSpreadsheetColumnsFixable(List<? extends SpreadsheetColumn> list) { + List<Integer> newList = new ArrayList<>(); + for (SpreadsheetColumn column : list) { + if (column != null) { + newList.add(columns.indexOf(column)); + } + } + return areColumnsFixable(newList); + } + + /** + * This method is the same as {@link #areSpreadsheetColumnsFixable(java.util.List) + * } but is using a List of {@link SpreadsheetColumn} indexes. + * + * A set of columns cannot be fixed if any cell inside these columns has a + * column span superior to the number of fixed columns. + * + * @param list + * @return true if the List of columns can be fixed together. + */ + public boolean areColumnsFixable(List<? extends Integer> list) { + if (list == null || list.isEmpty() || !isFixingRowsAllowed()) { + return false; + } + final Grid grid = getGrid(); + final int columnCount = grid.getColumnCount(); + final ObservableList<ObservableList<SpreadsheetCell>> rows = grid.getRows(); + for (Integer columnIndex : list) { + if (columnIndex == null || columnIndex < 0 || columnIndex >= columnCount) { + return false; + } + //If this column is not fixable, we need to identify the maximum span + if (!isColumnFixable(columnIndex)) { + int maxSpan = 1; + SpreadsheetCell cell; + for (List<SpreadsheetCell> row : rows) { + cell = row.get(columnIndex); + //If the original column is not within this range, there is not need to look deeper. + if (!list.contains(cell.getColumn())) { + return false; + } + //We only want to consider the original cell. + if (cell.getColumnSpan() > maxSpan && cell.getColumn() == columnIndex) { + maxSpan = cell.getColumnSpan(); + } + } + //Then we need to verify that all columns within that span are fixed. + int count = columnIndex + maxSpan - 1; + for (int index = columnIndex + 1; index <= count; ++index) { + if (!list.contains(index)) { + return false; + } + } + } + } + return true; + } + + /** + * Return whether change to Fixed columns are allowed. + * + * @return whether change to Fixed columns are allowed. + */ + public boolean isFixingColumnsAllowed() { + return fixingColumnsAllowedProperty.get(); + } + + /** + * If set to true, user will be allowed to fix and unfix the columns. + * + * @param b + */ + public void setFixingColumnsAllowed(boolean b) { + fixingColumnsAllowedProperty.set(b); + } + + /** + * Return the Boolean property associated with the allowance of fixing or + * unfixing some columns. + * + * @return the Boolean property associated with the allowance of fixing or + * unfixing some columns. + */ + public ReadOnlyBooleanProperty fixingColumnsAllowedProperty() { + return fixingColumnsAllowedProperty; + } + + /** + * Activate and deactivate the Column Header + * + * @param b + */ + public final void setShowColumnHeader(final boolean b) { + showColumnHeader.setValue(b); + } + + /** + * Return if the Column Header is showing. + * + * @return a boolean telling whether the column Header is shown + */ + public final boolean isShowColumnHeader() { + return showColumnHeader.get(); + } + + /** + * BooleanProperty associated with the column Header. + * + * @return the BooleanProperty associated with the column Header. + */ + public final BooleanProperty showColumnHeaderProperty() { + return showColumnHeader; + } + + /** + * Activate and deactivate the Row Header. + * + * @param b + */ + public final void setShowRowHeader(final boolean b) { + showRowHeader.setValue(b); + } + + /** + * Return if the row Header is showing. + * + * @return a boolean telling if the row Header is being shown + */ + public final boolean isShowRowHeader() { + return showRowHeader.get(); + } + + /** + * BooleanProperty associated with the row Header. + * + * @return the BooleanProperty associated with the row Header. + */ + public final BooleanProperty showRowHeaderProperty() { + return showRowHeader; + } + + /** + * This DoubleProperty represents the with of the rowHeader. This is just + * representing the width of the Labels, not the pickers. + * + * @return A DoubleProperty. + */ + public final DoubleProperty rowHeaderWidthProperty(){ + return rowHeaderWidth; + } + + /** + * Specify a new width for the row header. + * + * @param value + */ + public final void setRowHeaderWidth(double value){ + rowHeaderWidth.setValue(value); + } + + /** + * + * @return the current width of the row header. + */ + public final double getRowHeaderWidth(){ + return rowHeaderWidth.get(); + } + + /** + * @return An ObservableMap with the row index as key and the Picker as a + * value. + */ + public ObservableMap<Integer, Picker> getRowPickers() { + return rowPickers; + } + + /** + * @return An ObservableMap with the column index as key and the Picker as a + * value. + */ + public ObservableMap<Integer, Picker> getColumnPickers() { + return columnPickers; + } + + /** + * This method will compute the best height for each line. That is to say + * a height where each content of each cell could be fully visible.\n + * Use this method wisely because it can degrade performance on great grid. + */ + public void resizeRowsToFitContent() { + if (getCellsViewSkin() != null) { + getCellsViewSkin().resizeRowsToFitContent(); + } + } + + /** + * This method will first apply {@link #resizeRowsToFitContent() } and then + * take the highest height and apply it to every row.\n + * Just as {@link #resizeRowsToFitContent() }, this method can be degrading + * your performance on great grid. + */ + public void resizeRowsToMaximum(){ + if (getCellsViewSkin() != null) { + getCellsViewSkin().resizeRowsToMaximum(); + } + } + + /** + * This method will wipe all changes made to the row's height and set all row's + * height back to their default height defined in the model Grid. + */ + public void resizeRowsToDefault() { + if (getCellsViewSkin() != null) { + getCellsViewSkin().resizeRowsToDefault(); + } + } + + /** + * @param row + * @return the height of a particular row of the SpreadsheetView. + */ + public double getRowHeight(int row) { + //Sometime, the skin is not initialised yet.. + if (getCellsViewSkin() == null) { + return getGrid().getRowHeight(row); + } else { + return getCellsViewSkin().getRowHeight(row); + } + } + + /** + * Return the selectionModel used by the SpreadsheetView. + * + * @return {@link SpreadsheetViewSelectionModel} + */ + public SpreadsheetViewSelectionModel getSelectionModel() { + return selectionModel; + } + + /** + * Scrolls the SpreadsheetView so that the given row is visible. + * @param row + */ + public void scrollToRow(int row){ + cellsView.scrollTo(row); + } + + /** + * Same method as {@link ScrollBar#setValue(double) } on the verticalBar. + * + * @param value + */ + public void setVBarValue(double value) { + if (getCellsViewSkin() == null) { + Platform.runLater(() -> { + setVBarValue(value); + }); + return; + } + getCellsViewSkin().getVBar().setValue(value); + } + + /** + * Same method as {@link ScrollBar#setValue(double) } on the verticalBar. + * + * @param value + */ + public void setHBarValue(double value) { + setHBarValue(value,0); + } + + private void setHBarValue(double value, int attempt) { + if(attempt > 10){ + return; + } + if (getCellsViewSkin() == null) { + final int newAttempt = ++attempt; + Platform.runLater(() -> { + setHBarValue(value, newAttempt); + }); + return; + } + getCellsViewSkin().setHbarValue(value); + } + + /** + * Return the value of the vertical scrollbar. See {@link ScrollBar#getValue() + * } + * + * @return + */ + public double getVBarValue() { + if (getCellsViewSkin() != null && getCellsViewSkin().getVBar() != null) { + return getCellsViewSkin().getVBar().getValue(); + } + return 0.0; + } + + /** + * Return the value of the horizontal scrollbar. See {@link ScrollBar#getValue() + * } + * + * @return + */ + public double getHBarValue() { + if (getCellsViewSkin() != null && getCellsViewSkin().getHBar() != null) { + return getCellsViewSkin().getHBar().getValue(); + } + return 0.0; + } + + /** + * Scrolls the SpreadsheetView so that the given {@link SpreadsheetColumn} is visible. + * @param column + */ + public void scrollToColumn(SpreadsheetColumn column){ + cellsView.scrollToColumn(column.column); + } + + /** + * + * Scrolls the SpreadsheetView so that the given column index is visible. + * + * @param columnIndex + * + */ + public void scrollToColumnIndex(int columnIndex) { + cellsView.scrollToColumnIndex(columnIndex); + } + + /** + * Return the editor associated with the CellType. (defined in + * {@link SpreadsheetCellType#createEditor(SpreadsheetView)}. FIXME Maybe + * keep the editor references inside the SpreadsheetCellType + * + * @param cellType + * @return the editor associated with the CellType. + */ + public final Optional<SpreadsheetCellEditor> getEditor(SpreadsheetCellType<?> cellType) { + if(cellType == null){ + return Optional.empty(); + } + SpreadsheetCellEditor cellEditor = editors.get(cellType); + if (cellEditor == null) { + cellEditor = cellType.createEditor(this); + if(cellEditor == null){ + return Optional.empty(); + } + editors.put(cellType, cellEditor); + } + return Optional.of(cellEditor); + } + + /** + * Sets the value of the property editable. + * + * @param b + */ + public final void setEditable(final boolean b) { + cellsView.setEditable(b); + } + + /** + * Gets the value of the property editable. + * + * @return a boolean telling if the SpreadsheetView is editable. + */ + public final boolean isEditable() { + return cellsView.isEditable(); + } + + /** + * Specifies whether this SpreadsheetView is editable - only if the + * SpreadsheetView, and the {@link SpreadsheetCell} within it are both + * editable will a {@link SpreadsheetCell} be able to go into its editing + * state. + * + * @return the BooleanProperty associated with the editableProperty. + */ + public final BooleanProperty editableProperty() { + return cellsView.editableProperty(); + } + + /** + * This Node is shown to the user when the SpreadsheetView has no content to show. + */ + public final ObjectProperty<Node> placeholderProperty() { + return cellsView.placeholderProperty(); + } + + /** + * Sets the value of the placeholder property + * + * @param placeholder the node to show when the SpreadsheetView has no content to show. + */ + public final void setPlaceholder(final Node placeholder) { + cellsView.setPlaceholder(placeholder); + } + + /** + * Gets the value of the placeholder property. + * + * @return the Node used as a placeholder that is shown when the SpreadsheetView has no content to show. + */ + public final Node getPlaceholder() { + return cellsView.getPlaceholder(); + } + + + /*************************************************************************** + * COPY / PASTE METHODS + **************************************************************************/ + + /** + * Put the current selection into the ClipBoard. This can be overridden by + * developers for custom behavior. + */ + public void copyClipboard() { + checkFormat(); + + final ArrayList<GridChange> list = new ArrayList<>(); + final ObservableList<TablePosition> posList = getSelectionModel().getSelectedCells(); + + for (final TablePosition<?, ?> p : posList) { + SpreadsheetCell cell = getGrid().getRows().get(p.getRow()).get(p.getColumn()); + // Using SpreadsheetCell change to stock the information + // FIXME a dedicated class should be used + /** + * We need to add every cell contained in a span otherwise the + * rectangles computed when pasting will be wrong. + */ + for (int row = 0; row < cell.getRowSpan(); ++row) { + for (int col = 0; col < cell.getColumnSpan(); ++col) { + try { + new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(cell.getItem()); + list.add(new GridChange(cell.getRow() + row, cell.getColumn() + col, null, cell.getItem() == null ? null : cell.getItem())); + } catch (IOException exception) { + list.add(new GridChange(cell.getRow() + row, cell.getColumn() + col, null, cell.getItem() == null ? null : cell.getItem().toString())); + } + } + } + } + final ClipboardContent content = new ClipboardContent(); + content.put(fmt, list); + Clipboard.getSystemClipboard().setContent(content); + } + + /** + * Paste one value from the clipboard over the whole selection. + * @param change + */ + private void pasteOneValue(GridChange change) { + for (TablePosition position : getSelectionModel().getSelectedCells()) { + tryPasteCell(position.getRow(), position.getColumn(), change.getNewValue()); + } + } + + /** + * Try to paste the given value into the given position. + * @param row + * @param column + * @param value + */ + private void tryPasteCell(int row, int column, Object value) { + final SpanType type = getSpanType(row, column); + if (type == SpanType.NORMAL_CELL || type == SpanType.ROW_VISIBLE) { + SpreadsheetCell cell = getGrid().getRows().get(row).get(column); + boolean succeed = cell.getCellType().match(value); + if (succeed) { + getGrid().setCellValue(cell.getRow(), cell.getColumn(), + cell.getCellType().convertValue(value)); + } + } + } + + /** + * Try to paste the values given into the selection. If both selection are + * rectangles and the number of rows of the source is equal of the numbers + * of rows of the target AND number of columns of the target is a multiple + * of the number of columns of the source, then we can paste. + * + * Same goes if we invert the rows and columns. + * @param list + */ + private void pasteMixedValues(ArrayList<GridChange> list) { + SelectionRange sourceSelectionRange = new SelectionRange(); + sourceSelectionRange.fillGridRange(list); + + //It means we have a rectangle. + if (sourceSelectionRange.getRange() != null) { + SelectionRange targetSelectionRange = new SelectionRange(); + targetSelectionRange.fill(cellsView.getSelectionModel().getSelectedCells()); + if (targetSelectionRange.getRange() != null) { + //If both selection are rectangle + GridRange sourceRange = sourceSelectionRange.getRange(); + GridRange targetRange = targetSelectionRange.getRange(); + int sourceRowGap = sourceRange.getBottom() - sourceRange.getTop() + 1; + int targetRowGap = targetRange.getBottom() - targetRange.getTop() + 1; + + int sourceColumnGap = sourceRange.getRight() - sourceRange.getLeft() + 1; + int targetColumnGap = targetRange.getRight() - targetRange.getLeft() + 1; + + final int offsetRow = targetRange.getTop() - sourceRange.getTop(); + final int offsetCol = targetRange.getLeft() - sourceRange.getLeft(); + + //If the numbers of rows are the same and the targetColumnGap is a multiple of sourceColumnGap + if ((sourceRowGap == targetRowGap || targetRowGap == 1) && (targetColumnGap % sourceColumnGap) == 0) { + for (final GridChange change : list) { + int row = change.getRow() + offsetRow; + int column = change.getColumn() + offsetCol; + do { + if (row < getGrid().getRowCount() && column < getGrid().getColumnCount() + && row >= 0 && column >= 0) { + tryPasteCell(row, column, change.getNewValue()); + } + } while ((column = column + sourceColumnGap) <= targetRange.getRight()); + } + //If the numbers of columns are the same and the targetRowGap is a multiple of sourceRowGap + } else if ((sourceColumnGap == targetColumnGap || targetColumnGap == 1) && (targetRowGap % sourceRowGap) == 0) { + for (final GridChange change : list) { + + int row = change.getRow() + offsetRow; + int column = change.getColumn() + offsetCol; + do { + if (row < getGrid().getRowCount() && column < getGrid().getColumnCount() + && row >= 0 && column >= 0) { + tryPasteCell(row, column, change.getNewValue()); + } + } while ((row = row + sourceRowGap) <= targetRange.getBottom()); + } + } + } + } + } + + /** + * If we have several source values to paste into one cell, we do it. + * + * @param list + */ + private void pasteSeveralValues(ArrayList<GridChange> list) { + // TODO algorithm very bad + int minRow = getGrid().getRowCount(); + int minCol = getGrid().getColumnCount(); + int maxRow = 0; + int maxCol = 0; + for (final GridChange p : list) { + final int tempcol = p.getColumn(); + final int temprow = p.getRow(); + if (tempcol < minCol) { + minCol = tempcol; + } + if (tempcol > maxCol) { + maxCol = tempcol; + } + if (temprow < minRow) { + minRow = temprow; + } + if (temprow > maxRow) { + maxRow = temprow; + } + } + + final TablePosition<?, ?> p = cellsView.getFocusModel().getFocusedCell(); + + final int offsetRow = p.getRow() - minRow; + final int offsetCol = p.getColumn() - minCol; + final int rowCount = getGrid().getRowCount(); + final int columnCount = getGrid().getColumnCount(); + int row; + int column; + + for (final GridChange change : list) { + row = change.getRow() + offsetRow; + column = change.getColumn() + offsetCol; + if (row < rowCount && column < columnCount + && row >= 0 && column >= 0) { + tryPasteCell(row, column, change.getNewValue()); + } + } + } + + /** + * Try to paste the clipBoard to the specified position. Try to paste the + * current selection into the Grid. If the two contents are not matchable, + * then it's not pasted. This can be overridden by developers for custom + * behavior. + */ + public void pasteClipboard() { + // FIXME Maybe move editableProperty to the model.. + List<TablePosition> selectedCells = cellsView.getSelectionModel().getSelectedCells(); + if (!isEditable() || selectedCells.isEmpty()) { + return; + } + + checkFormat(); + final Clipboard clipboard = Clipboard.getSystemClipboard(); + + if (clipboard.getContent(fmt) != null) { + + @SuppressWarnings("unchecked") + final ArrayList<GridChange> list = (ArrayList<GridChange>) clipboard.getContent(fmt); + if (list.size() == 1) { + pasteOneValue(list.get(0)); + } else if (selectedCells.size() > 1) { + pasteMixedValues(list); + } else { + pasteSeveralValues(list); + } + // To be improved + } else if (clipboard.hasString()) { + ArrayList<GridChange> list = new ArrayList<>(); + String str = clipboard.getString(); + + String[] lines = str.split("\r"); + for(int i = 0; i < lines.length; ++i) { + String line = lines[i]; + String[] cells = line.split("\t"); + + for(int j = 0; j < cells.length; ++ j) { + String cell = cells[j]; + list.add(new GridChange(i, j, cell, cell)); + } + + } + + if (list.size() == 1) { + pasteOneValue(list.get(0)); + } else if (selectedCells.size() > 1) { + pasteMixedValues(list); + } else { + pasteSeveralValues(list); + } + + /* + final TablePosition<?,?> p = + cellsView.getFocusModel().getFocusedCell(); + // + SpreadsheetCell stringCell = + SpreadsheetCellType.STRING.createCell(0, 0, 1, 1, + clipboard.getString()); + getGrid().getRows().get(p.getRow()).get(p.getColumn()).match(stringCell);*/ + } + } + + /** + * Create a menu on rightClick with two options: Copy/Paste This can be + * overridden by developers for custom behavior. + * + * @return the ContextMenu to use. + */ + public ContextMenu getSpreadsheetViewContextMenu() { + final ContextMenu contextMenu = new ContextMenu(); + + final MenuItem copyItem = new MenuItem(localize(asKey("spreadsheet.view.menu.copy"))); //$NON-NLS-1$ + copyItem.setGraphic(new ImageView(new Image(SpreadsheetView.class + .getResourceAsStream("copySpreadsheetView.png")))); //$NON-NLS-1$ + copyItem.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN)); + copyItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + copyClipboard(); + } + }); + + final MenuItem pasteItem = new MenuItem(localize(asKey("spreadsheet.view.menu.paste"))); //$NON-NLS-1$ + pasteItem.setGraphic(new ImageView(new Image(SpreadsheetView.class + .getResourceAsStream("pasteSpreadsheetView.png")))); //$NON-NLS-1$ + pasteItem.setAccelerator(new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN)); + pasteItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent e) { + pasteClipboard(); + } + }); + + final Menu cornerMenu = new Menu(localize(asKey("spreadsheet.view.menu.comment"))); //$NON-NLS-1$ + cornerMenu.setGraphic(new ImageView(new Image(SpreadsheetView.class + .getResourceAsStream("comment.png")))); //$NON-NLS-1$ + + final MenuItem topLeftItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.top-left"))); //$NON-NLS-1$ + topLeftItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + cell.activateCorner(SpreadsheetCell.CornerPosition.TOP_LEFT); + } + }); + final MenuItem topRightItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.top-right"))); //$NON-NLS-1$ + topRightItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + cell.activateCorner(SpreadsheetCell.CornerPosition.TOP_RIGHT); + } + }); + final MenuItem bottomRightItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.bottom-right"))); //$NON-NLS-1$ + bottomRightItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + cell.activateCorner(SpreadsheetCell.CornerPosition.BOTTOM_RIGHT); + } + }); + final MenuItem bottomLeftItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment.bottom-left"))); //$NON-NLS-1$ + bottomLeftItem.setOnAction(new EventHandler<ActionEvent>() { + + @Override + public void handle(ActionEvent t) { + TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell(); + SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn()); + cell.activateCorner(SpreadsheetCell.CornerPosition.BOTTOM_LEFT); + } + }); + + cornerMenu.getItems().addAll(topLeftItem, topRightItem, bottomRightItem, bottomLeftItem); + + contextMenu.getItems().addAll(copyItem, pasteItem, cornerMenu); + return contextMenu; + } + + /** + * This method is called when pressing the "delete" key on the + * SpreadsheetView. This will erase the values of selected cells. This can + * be overridden by developers for custom behavior. + */ + public void deleteSelectedCells() { + for (TablePosition<ObservableList<SpreadsheetCell>, ?> position : getSelectionModel().getSelectedCells()) { + getGrid().setCellValue(position.getRow(), position.getColumn(), null); + } + } + + /** + * Return the {@link SpanType} of a cell, this is a shorcut for + * {@link Grid#getSpanType(org.controlsfx.control.spreadsheet.SpreadsheetView, int, int) }. + * + * @param row + * @param column + * @return The {@link SpanType} of a cell + */ + public SpanType getSpanType(final int row, final int column) { + if (getGrid() == null) { + return SpanType.NORMAL_CELL; + } + return getGrid().getSpanType(this, row, column); + } + + /*************************************************************************** + * * Private/Protected Implementation * * + **************************************************************************/ + + /** + * This is called when setting a Grid. The main idea is to re-use + * TableColumn if possible. Because we can have a great amount of time spent + * in com.sun.javafx.css.StyleManager.forget when removing lots of columns + * and adding new ones. So if we already have some, we can just re-use them + * so we avoid doign all the fuss with the TableColumns. + * + * @param grid + * @param columnIndex + * @return + */ + private TableColumn<ObservableList<SpreadsheetCell>, SpreadsheetCell> getTableColumn(Grid grid, int columnIndex) { + + TableColumn<ObservableList<SpreadsheetCell>, SpreadsheetCell> column; + + String columnHeader = grid.getColumnHeaders().size() > columnIndex ? grid + .getColumnHeaders().get(columnIndex) : Utils.getExcelLetterFromNumber(columnIndex); + + if (columnIndex < cellsView.getColumns().size()) { + column = (TableColumn<ObservableList<SpreadsheetCell>, SpreadsheetCell>) cellsView.getColumns().get(columnIndex); + column.setText(columnHeader); + } else { + column = new TableColumn<>(columnHeader); + + column.setEditable(true); + // We don't want to sort the column + column.setSortable(false); + + column.impl_setReorderable(false); + + // We assign a DataCell for each Cell needed (MODEL). + column.setCellValueFactory((TableColumn.CellDataFeatures<ObservableList<SpreadsheetCell>, SpreadsheetCell> p) -> { + if (columnIndex >= p.getValue().size()) { + return null; + } + return new ReadOnlyObjectWrapper<>(p.getValue().get(columnIndex)); + }); + // We create a SpreadsheetCell for each DataCell in order to + // specify how to represent the DataCell(VIEW) + column.setCellFactory((TableColumn<ObservableList<SpreadsheetCell>, SpreadsheetCell> p) -> new CellView(handle)); + } + return column; + } + + /** + * This static method creates a sample Grid with 100 rows and 15 columns. + * All cells are typed as String. + * + * @return the sample Grid + * @see SpreadsheetCellType#STRING + */ + private static Grid getSampleGrid() { + GridBase gridBase = new GridBase(100, 15); + List<ObservableList<SpreadsheetCell>> rows = FXCollections.observableArrayList(); + + for (int row = 0; row < gridBase.getRowCount(); ++row) { + ObservableList<SpreadsheetCell> currentRow = FXCollections.observableArrayList(); + for (int column = 0; column < gridBase.getColumnCount(); ++column) { + currentRow.add(SpreadsheetCellType.STRING.createCell(row, column, 1, 1, "toto")); + } + rows.add(currentRow); + } + gridBase.setRows(rows); + return gridBase; + } + + private void initRowFix(Grid grid) { + ObservableList<ObservableList<SpreadsheetCell>> rows = grid.getRows(); + rowFix = new BitSet(rows.size()); + rows: + for (int r = 0; r < rows.size(); ++r) { + ObservableList<SpreadsheetCell> row = rows.get(r); + for (SpreadsheetCell cell : row) { + if (cell.getRowSpan() > 1) { + continue rows; + } + } + rowFix.set(r); + } + } + + /** + * Verify that the grid is well-formed. Can be quite time-consuming I guess + * so I would like it not to be compulsory.. + * + * @param grid + */ + private void verifyGrid(Grid grid) { + verifyColumnSpan(grid); + } + + private void verifyColumnSpan(Grid grid) { + for (int i = 0; i < grid.getRows().size(); ++i) { + ObservableList<SpreadsheetCell> row = grid.getRows().get(i); + int count = 0; + for (int j = 0; j < row.size(); ++j) { + if (row.get(j).getColumnSpan() == 1) { + ++count; + } else if (row.get(j).getColumnSpan() > 1) { + ++count; + SpreadsheetCell currentCell = row.get(j); + for (int k = j + 1; k < currentCell.getColumn() + currentCell.getColumnSpan(); ++k) { + if (!row.get(k).equals(currentCell)) { + throw new IllegalStateException("\n At row " + i + " and column " + j //$NON-NLS-1$ //$NON-NLS-2$ + + ": this cell is in the range of a columnSpan but is different. \n" //$NON-NLS-1$ + + "Every cell in a range of a ColumnSpan must be of the same instance."); //$NON-NLS-1$ + } + ++count; + ++j; + } + } else { + throw new IllegalStateException("\n At row " + i + " and column " + j //$NON-NLS-1$ //$NON-NLS-2$ + + ": this cell has a negative columnSpan"); //$NON-NLS-1$ + } + } + if (count != grid.getColumnCount()) { + throw new IllegalStateException("The row" + i //$NON-NLS-1$ + + " has a number of cells different of the columnCount declared in the grid."); //$NON-NLS-1$ + } + } + } + + private void checkFormat() { + if ((fmt = DataFormat.lookupMimeType("SpreadsheetView")) == null) { //$NON-NLS-1$ + fmt = new DataFormat("SpreadsheetView"); //$NON-NLS-1$ + } + } + + /** + * ********************************************************************* * + * private listeners + * ******************************************************************** + */ + + private final ListChangeListener<Integer> fixedRowsListener = new ListChangeListener<Integer>() { + @Override + public void onChanged(ListChangeListener.Change<? extends Integer> c) { + while (c.next()) { + if (c.wasAdded()) { + List<? extends Integer> newRows = c.getAddedSubList(); + if(!areRowsFixable(newRows)){ + throw new IllegalArgumentException(computeReason(newRows)); + } + FXCollections.sort(fixedRows); + } + + if(c.wasRemoved()){ + //Handle this case. + } + } + } + }; + + private String computeReason(List<? extends Integer> list) { + String reason = "\n A row cannot be fixed. \n"; //$NON-NLS-1$ + + for (Integer row : list) { + //If this row is not fixable, we need to identify the maximum span + if (!isRowFixable(row)) { + + int maxSpan = 1; + List<SpreadsheetCell> gridRow = getGrid().getRows().get(row); + for (SpreadsheetCell cell : gridRow) { + if(!list.contains(cell.getRow())){ + reason += "The row " + row + " is inside a row span and the starting row " + cell.getRow() + " is not fixed.\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + //We only want to consider the original cell. + if (cell.getRowSpan() > maxSpan && cell.getRow() == row) { + maxSpan = cell.getRowSpan(); + } + } + //Then we need to verify that all rows within that span are fixed. + int count = row + maxSpan - 1; + for (int index = row + 1; index < count; ++index) { + if (!list.contains(index)) { + reason += "One cell on the row " + row + " has a row span of " + maxSpan + ". " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + "But the row " + index + " contained within that span is not fixed.\n"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + } + return reason; + } + + private final ListChangeListener<SpreadsheetColumn> fixedColumnsListener = new ListChangeListener<SpreadsheetColumn>() { + @Override + public void onChanged(ListChangeListener.Change<? extends SpreadsheetColumn> c) { + while (c.next()) { + if (c.wasAdded()) { + List<? extends SpreadsheetColumn> newColumns = c.getAddedSubList(); + if (!areSpreadsheetColumnsFixable(newColumns)) { + List<Integer> newList = new ArrayList<>(); + for (SpreadsheetColumn column : newColumns) { + if (column != null) { + newList.add(columns.indexOf(column)); + } + } + throw new IllegalArgumentException(computeReason(newList)); + } + } + } + } + + private String computeReason(List<Integer> list) { + + String reason = "\n This column cannot be fixed."; //$NON-NLS-1$ + final ObservableList<ObservableList<SpreadsheetCell>> rows = getGrid().getRows(); + for (Integer columnIndex : list) { + //If this row is not fixable, we need to identify the maximum span + if (!isColumnFixable(columnIndex)) { + int maxSpan = 1; + SpreadsheetCell cell; + for (List<SpreadsheetCell> row : rows) { + cell = row.get(columnIndex); + //If the original column is not within this range, there is not need to look deeper. + if (!list.contains(cell.getColumn())) { + reason += "The column " + columnIndex + " is inside a column span and the starting column " + cell.getColumn() + " is not fixed.\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + //We only want to consider the original cell. + if (cell.getColumnSpan() > maxSpan && cell.getColumn() == columnIndex) { + maxSpan = cell.getColumnSpan(); + } + } + //Then we need to verify that all columns within that span are fixed. + int count = columnIndex + maxSpan - 1; + for (int index = columnIndex + 1; index < count; ++index) { + if (!list.contains(index)) { + reason += "One cell on the column " + columnIndex + " has a column span of " + maxSpan + ". " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + "But the column " + index + " contained within that span is not fixed.\n"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + } + return reason; + } + }; + + private final ChangeListener<ContextMenu> contextMenuChangeListener = new ChangeListener<ContextMenu>() { + + @Override + public void changed(ObservableValue<? extends ContextMenu> arg0, ContextMenu oldContextMenu, final ContextMenu newContextMenu) { + if(oldContextMenu !=null){ + oldContextMenu.setOnShowing(null); + } + if(newContextMenu != null){ + newContextMenu.setOnShowing(new WeakEventHandler<>(hideContextMenuEventHandler)); + } + } + }; + + private final EventHandler<WindowEvent> hideContextMenuEventHandler = new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent arg0) { + // We don't want to open a contextMenu when editing + // because editors + // have their own contextMenu + if (getEditingCell() != null) { + // We're being reactive but we want to be pro-active + // so we may need a work-around. + Platform.runLater(()->{ + getContextMenu().hide(); + }); + } + } + }; + + private final EventHandler<KeyEvent> keyPressedHandler = (KeyEvent keyEvent) -> { + TablePosition<ObservableList<SpreadsheetCell>, ?> position = getSelectionModel().getFocusedCell(); + // Go to the next row only if we're not editing + if (getEditingCell() == null && KeyCode.ENTER.equals(keyEvent.getCode())) { + if (position != null) { + if(keyEvent.isShiftDown()){ + getSelectionModel().clearAndSelectPreviousCell(); + }else{ + getSelectionModel().clearAndSelectNextCell(); + } + //We consume the event because we don't want to go in edition + keyEvent.consume(); + } + getCellsViewSkin().scrollHorizontally(); + // Go to next cell + } else if (getEditingCell() == null && KeyCode.TAB.equals(keyEvent.getCode())) { + if (position != null) { + if (keyEvent.isShiftDown()) { + getSelectionModel().clearAndSelectLeftCell(); + } else { + getSelectionModel().clearAndSelectRightCell(); + } + } + //We consume the event because we don't want to loose focus + keyEvent.consume(); + getCellsViewSkin().scrollHorizontally(); + // We want to erase values when delete key is pressed. + } else if (KeyCode.DELETE.equals(keyEvent.getCode())) { + deleteSelectedCells(); + /** + * We want NOT to go in edition if we're pressing SHIFT and if we're + * using the navigation keys. But we still want the user to go in + * edition with SHIFT and some letters for example if he wants a + * capital letter. + * FIXME Add a test to prevent the Shift fail case. + */ + }else if (keyEvent.getCode() != KeyCode.SHIFT && !keyEvent.isShortcutDown() + && !keyEvent.getCode().isNavigationKey() + && keyEvent.getCode() != KeyCode.ESCAPE) { + getCellsView().edit(position.getRow(), position.getTableColumn()); + } + }; + + /** + * This event is thrown on the SpreadsheetView when the user resize a row + * with its mouse. + */ + public static class RowHeightEvent extends Event { + + /** + * This is the event used by {@link RowHeightEvent}. + */ + public static final EventType<RowHeightEvent> ROW_HEIGHT_CHANGE = new EventType<>(Event.ANY, "RowHeightChange"); //$NON-NLS-1$ + + private final int row; + private final double height; + + public RowHeightEvent(int row, double height) { + super(ROW_HEIGHT_CHANGE); + this.row = row; + this.height = height; + } + + /** + * Return the row index that has been resized. + * @return the row index that has been resized. + */ + public int getRow() { + return row; + } + + /** + * Return the new height for this row. + * @return the new height for this row. + */ + public double getHeight() { + return height; + } + } + + /** + * This event is thrown on the SpreadsheetView when the user resize a column + * with its mouse. + */ + public static class ColumnWidthEvent extends Event { + + /** + * This is the event used by {@link ColumnWidthEvent}. + */ + public static final EventType<ColumnWidthEvent> COLUMN_WIDTH_CHANGE = new EventType<>(Event.ANY, "ColumnWidthChange"); //$NON-NLS-1$ + + private final int column; + private final double width; + + public ColumnWidthEvent(int column, double width) { + super(COLUMN_WIDTH_CHANGE); + this.column = column; + this.width = width; + } + + /** + * Return the column index that has been resized. + * @return the column index that has been resized. + */ + public int getColumn() { + return column; + } + + /** + * Return the new width for this column. + * @return the new width for this column. + */ + public double getWidth() { + return width; + } + } + + public void addedSkin(GridViewSkin viewSkin) { + // TODO Auto-generated method stub + + } +} diff --git a/src/org/controlsfx/control/spreadsheet/SpreadsheetViewSelectionModel.java b/src/org/controlsfx/control/spreadsheet/SpreadsheetViewSelectionModel.java new file mode 100644 index 0000000000000000000000000000000000000000..95611805ddabca158e2451e58d2750c0d1c22a92 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/SpreadsheetViewSelectionModel.java @@ -0,0 +1,237 @@ +/** + * Copyright (c) 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import impl.org.controlsfx.spreadsheet.FocusModelListener; +import impl.org.controlsfx.spreadsheet.TableViewSpanSelectionModel; +import java.util.Arrays; +import java.util.List; +import javafx.collections.ObservableList; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.util.Pair; + +/** + * + * This class provides basic support for common interaction on the + * {@link SpreadsheetView}. + * + * Due to the complexity induced by cell's span, it is not possible to give a + * full access to selectionModel like in the {@link TableView}. + */ +public class SpreadsheetViewSelectionModel { + + private final TableViewSpanSelectionModel selectionModel; + private final SpreadsheetView spv; + + SpreadsheetViewSelectionModel(SpreadsheetView spv, TableViewSpanSelectionModel selectionModel) { + this.spv = spv; + this.selectionModel = selectionModel; + } + + /** + * Clears all selection, and then selects the cell at the given row/column intersection. + * @param row + * @param column + */ + public final void clearAndSelect(int row, SpreadsheetColumn column) { + selectionModel.clearAndSelect(row, column.column); + } + + /** + * Selects the cell at the given row/column intersection. + * @param row + * @param column + */ + public final void select(int row, SpreadsheetColumn column) { + selectionModel.select(row,column.column); + } + + /** + * Clears the selection model of all selected indices. + */ + public final void clearSelection() { + selectionModel.clearSelection(); + } + + /** + * A read-only ObservableList representing the currently selected cells in this SpreadsheetView. + * @return A read-only ObservableList. + */ + public final ObservableList<TablePosition> getSelectedCells() { + return selectionModel.getSelectedCells(); + } + + /** + * Select all the possible cells. + */ + public final void selectAll() { + selectionModel.selectAll(); + } + + /** + * Return the position of the cell that has current focus. + * @return the position of the cell that has current focus. + */ + public final TablePosition getFocusedCell(){ + return selectionModel.getTableView().getFocusModel().getFocusedCell(); + } + + /** + * Causes the cell at the given index to receive the focus. + * @param row The row index of the item to give focus to. + * @param column The column of the item to give focus to. Can be null. + */ + public final void focus(int row, SpreadsheetColumn column){ + selectionModel.getTableView().getFocusModel().focus(row, column.column); + } + + /** + * Specifies the selection mode to use in this selection model. The + * selection mode specifies how many items in the underlying data model can + * be selected at any one time. By default, the selection mode is + * {@link SelectionMode#MULTIPLE}. + * + * @param value + */ + public final void setSelectionMode(SelectionMode value) { + selectionModel.setSelectionMode(value); + } + + /** + * Return the selectionMode currently used. + * + * @return the selectionMode currently used. + */ + public SelectionMode getSelectionMode() { + return selectionModel.getSelectionMode(); + } + + + /** + * Use this method to select discontinuous cells. + * + * The {@link Pair} must contain the row index as key and the column index + * as value. This is useful when you want to select a great amount of cell + * because it will be more efficient than calling + * {@link #select(int, org.controlsfx.control.spreadsheet.SpreadsheetColumn) }. + * + * @param selectedCells + */ + public void selectCells(List<Pair<Integer, Integer>> selectedCells) { + selectionModel.verifySelectedCells(selectedCells); + } + + /** + * Use this method to select discontinuous cells. + * + * The {@link Pair} must contain the row index as key and the column index + * as value. This is useful when you want to select a great amount of cell + * because it will be more efficient than calling + * {@link #select(int, org.controlsfx.control.spreadsheet.SpreadsheetColumn) }. + * @param selectedCells + */ + public void selectCells(Pair<Integer, Integer>... selectedCells) { + selectionModel.verifySelectedCells(Arrays.asList(selectedCells)); + } + + /** + * Selects the cells in the range (minRow, minColumn) to (maxRow, maxColumn), inclusive. + * @param minRow + * @param minColumn + * @param maxRow + * @param maxColumn + */ + public void selectRange(int minRow, SpreadsheetColumn minColumn, int maxRow, SpreadsheetColumn maxColumn) { + selectionModel.selectRange(minRow, minColumn.column, maxRow, maxColumn.column); + } + + /** + * Clear the current selection and select the cell on the left of the + * current focused cell. If the cell is the first one on a row, the last + * cell of the preceding row is selected. + */ + public void clearAndSelectLeftCell() { + TablePosition<ObservableList<SpreadsheetCell>, ?> position = getFocusedCell(); + int row = position.getRow(); + int column = position.getColumn(); + column -= 1; + if (column < 0) { + if (row == 0) { + column++; + } else { + column = spv.getGrid().getColumnCount() - 1; + row--; + } + } + clearAndSelect(row, spv.getColumns().get(column)); + } + + /** + * Clear the current selection and select the cell on the right of the + * current focused cell. If the cell is the last one on a row, the first + * cell of the next row is selected. + */ + public void clearAndSelectRightCell() { + TablePosition<ObservableList<SpreadsheetCell>, ?> position = getFocusedCell(); + int row = position.getRow(); + int column = position.getColumn(); + column += 1; + if (column >= spv.getColumns().size()) { + if (row == spv.getGrid().getRowCount() - 1) { + column--; + } else { + column = 0; + row++; + } + } + clearAndSelect(row, spv.getColumns().get(column)); + } + + /** + * Clear the current selection and select the cell on the previous row. + */ + public void clearAndSelectPreviousCell() { + TablePosition<ObservableList<SpreadsheetCell>, ?> position = getFocusedCell(); + int nextRow = FocusModelListener.getPreviousRowNumber(position, selectionModel.getTableView()); + if (nextRow >= 0) { + clearAndSelect(nextRow, spv.getColumns().get(position.getColumn())); + } + } + + /** + * Clear the current selection and select the cell on the next row. + */ + public void clearAndSelectNextCell() { + TablePosition<ObservableList<SpreadsheetCell>, ?> position = getFocusedCell(); + int nextRow = FocusModelListener.getNextRowNumber(position, selectionModel.getTableView()); + if (nextRow < spv.getGrid().getRowCount()) { + clearAndSelect(nextRow, spv.getColumns().get(position.getColumn())); + } + } +} diff --git a/src/org/controlsfx/control/spreadsheet/StringConverterWithFormat.java b/src/org/controlsfx/control/spreadsheet/StringConverterWithFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..d0b4d41ea9866627c271d3076232c2b9dcde08b0 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/StringConverterWithFormat.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.spreadsheet; + +import javafx.util.StringConverter; + +/** + * This class is used by some of the {@link SpreadsheetCellType} in order to use + * a specific format.<br> + * + * Since the format is specified in the {@link SpreadsheetCell}, we need a + * converter which provide a runtime method {@link #toStringFormat(Object, String)}.<br> + * + * This class provide two constructors: + * <ul> + * <li>A default one where you implement the three abstract methods.</li> + * <li>Another one which takes another StringConverter. This is useful when you just want to implement + * the {@link #toStringFormat(Object, String)} and let the other converter handle the other methods.</li> + * </ul> + * + * @see SpreadsheetCellType + * + * @param <T> + */ +public abstract class StringConverterWithFormat<T> extends StringConverter<T> { + + protected StringConverter<T> myConverter; + + /** + * Default constructor. + */ + public StringConverterWithFormat() { + super(); + } + + /** + * This constructor allow to use another StringConverter. + * @param specificStringConverter + */ + public StringConverterWithFormat(StringConverter<T> specificStringConverter) { + myConverter = specificStringConverter; + } + + /** + * Converts the object provided into its string form with the specified format. + * @param value + * @param format + * @return a string containing the converted value with the specified format. + */ + public String toStringFormat(T value, String format) { + return toString(value); + } +} diff --git a/src/org/controlsfx/control/spreadsheet/comment.png b/src/org/controlsfx/control/spreadsheet/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..974c85b3a2c677ffedde2e977ce1224c5491e16e Binary files /dev/null and b/src/org/controlsfx/control/spreadsheet/comment.png differ diff --git a/src/org/controlsfx/control/spreadsheet/copySpreadsheetView.png b/src/org/controlsfx/control/spreadsheet/copySpreadsheetView.png new file mode 100644 index 0000000000000000000000000000000000000000..de6c54def339045f2f05589b2a7588582e393074 Binary files /dev/null and b/src/org/controlsfx/control/spreadsheet/copySpreadsheetView.png differ diff --git a/src/org/controlsfx/control/spreadsheet/package-info.java b/src/org/controlsfx/control/spreadsheet/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..15373ae82641cf8e9ad408c663a7d4b3fefb233d --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing model and view related classes used by the + * {@link org.controlsfx.control.spreadsheet.SpreadsheetView} control. + */ +package org.controlsfx.control.spreadsheet; \ No newline at end of file diff --git a/src/org/controlsfx/control/spreadsheet/pasteSpreadsheetView.png b/src/org/controlsfx/control/spreadsheet/pasteSpreadsheetView.png new file mode 100644 index 0000000000000000000000000000000000000000..62286418d514b129a875e4e075dcd38ee8d13277 Binary files /dev/null and b/src/org/controlsfx/control/spreadsheet/pasteSpreadsheetView.png differ diff --git a/src/org/controlsfx/control/spreadsheet/picker.png b/src/org/controlsfx/control/spreadsheet/picker.png new file mode 100644 index 0000000000000000000000000000000000000000..69a1701e57f3d10dec30f6658e17e4040fd6a81e Binary files /dev/null and b/src/org/controlsfx/control/spreadsheet/picker.png differ diff --git a/src/org/controlsfx/control/spreadsheet/pinSpreadsheetView.png b/src/org/controlsfx/control/spreadsheet/pinSpreadsheetView.png new file mode 100644 index 0000000000000000000000000000000000000000..6304b62b82f938fbad45fb32ef4495a818be2d1d Binary files /dev/null and b/src/org/controlsfx/control/spreadsheet/pinSpreadsheetView.png differ diff --git a/src/org/controlsfx/control/spreadsheet/spreadsheet.css b/src/org/controlsfx/control/spreadsheet/spreadsheet.css new file mode 100644 index 0000000000000000000000000000000000000000..516d3954fe36e26595a753ed44ab675b5090cd81 --- /dev/null +++ b/src/org/controlsfx/control/spreadsheet/spreadsheet.css @@ -0,0 +1,150 @@ +.cell-spreadsheet .table-row-cell { + -fx-background-color: transparent; +} + +/* NORMAL CELL */ +.spreadsheet-cell:filled:selected, +.spreadsheet-cell:filled:focused:selected, +.spreadsheet-cell:filled:focused:selected:hover { + -fx-background-color: #8cb1ff; + -fx-border-color: #a9a9a9; + -fx-border-width : 0.5px; + -fx-text-fill: -fx-selection-bar-text; + +} +.spreadsheet-cell:hover, +.spreadsheet-cell:filled:focused { + -fx-background-color: #988490; + -fx-text-fill: -fx-text-inner-color; + -fx-background-insets: 0, 0 0 1 0; +} + +.spreadsheet-cell{ + -fx-padding: 0 0 0 0.2em; + -fx-border-color: black; + -fx-border-width : 0.3px; + -fx-background-color: -fx-table-cell-border-color,white; +} + +.tooltip { + -fx-background-radius: 0px; + -fx-background-color: + linear-gradient(#cec340, #a59c31), + linear-gradient(#fefefc, #e6dd71), + linear-gradient(#fef592, #e5d848); + -fx-background-insets: 0,1,2; + -fx-padding: 0.333333em 0.666667em 0.333333em 0.666667em; /* 4 8 4 8 */ + -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 8, 0.0 , 0 , 0 ); + -fx-text-fill:black; +} + +/* FIXED HEADERS */ +VerticalHeader > Label.fixed{ + -fx-background-color: -fx-box-border, lightgray; + -fx-font-style : italic; +} + +HorizontalHeaderColumn > TableColumnHeader.column-header.table-column.fixed{ + -fx-background-color: -fx-box-border, lightgray; + -fx-font-style : italic; +} + +/* HORIZONTAL AND VERTICAL HEADER SELECTION */ +VerticalHeader > Label , +HorizontalHeaderColumn > TableColumnHeader.column-header.table-column{ + -fx-background-color: -fx-box-border, #F3F3F3; + -fx-background-insets: 0, 0 1 1 0, 1 2 2 1; + -fx-font-weight: bold; + -fx-size: 2em; + -fx-text-fill: -fx-selection-bar-text; + -fx-alignment: center; + -fx-font-style : normal; +} + +VerticalHeader > Label.selected{ + -fx-background-color: #8FB1E8; + -fx-text-fill :white; +} + +HorizontalHeaderColumn > TableColumnHeader.column-header.table-column.selected, +HorizontalHeaderColumn > TableColumnHeader.column-header.table-column.selected > Label +{ + -fx-background-color:#8FB1E8; + -fx-text-fill :white; +} + +/* HORIZONTAL HEADER VISIBILITY */ +.column-header-background.invisible { visibility: hidden; -fx-padding: -1em; } + +.cell-corner{ + -fx-background-color: red; +} + +.cell-corner.top-left{ + -fx-shape : "M 0 0 L 1 0 L 0 1 z"; +} + +.cell-corner.top-right{ + -fx-shape : "M 0 0 L -1 0 L 0 1 z"; +} + +.cell-corner.bottom-right{ + -fx-shape : "M 0 0 L -1 0 L 0 -1 z"; +} + +.cell-corner.bottom-left{ + -fx-shape : "M 0 0 L 1 0 L 0 -1 z"; +} + +.indicationLabel{ + -fx-font-style : italic; +} + +/* PICKERS */ +.picker-label{ + -fx-graphic: url("picker.png"); + -fx-background-color: white; + -fx-padding: 0 0 0 0; + -fx-alignment: center; +} + +.picker-label:hover{ + /*-fx-effect:dropshadow(gaussian, black, 10, 0.1, 0, 0);*/ + -fx-cursor:hand; +} + +/* We don't want to show the white background both for TextField +and textArea. We want it to be transparent just like Excel. + +Also we need to shift to the left the editor a bit*/ +CellView > .text-input.text-field{ + -fx-padding : 0 0 0 -0.2em; + -fx-background-color: transparent; +} +CellView > .text-input.text-area, +CellView > TextArea .scroll-pane > .viewport{ + -fx-background-color: transparent; +} + +/* I shift by 3px, it's not clean but it works for normal row (24px) as it +centers the textArea.*/ +CellView > TextArea .scroll-pane{ + -fx-padding : 3px 0 0 -0.15em; +} + +CellView > TextArea .scroll-pane > .viewport .content{ + -fx-padding : 0 0 0 0; + -fx-background-color: transparent; +} +/* The scrollBars must always have the same size because we may have +really big font in the editor (48px) and the scrollBars become obese otherwise.*/ +CellView >TextArea .scroll-bar:vertical , +CellView >TextArea .scroll-bar:horizontal { + -fx-font-size : 1em; +} + +.selection-rectangle{ + -fx-fill : transparent; + -fx-stroke : black; + -fx-stroke-width : 2; +} diff --git a/src/org/controlsfx/control/statusbar.css b/src/org/controlsfx/control/statusbar.css new file mode 100644 index 0000000000000000000000000000000000000000..09f669d64a430b5db433814f0632be1402a0e7c8 --- /dev/null +++ b/src/org/controlsfx/control/statusbar.css @@ -0,0 +1,6 @@ +.status-bar { + -fx-padding: 4px; + -fx-pref-height: 30px; + -fx-background-color: lightgray, -fx-body-color; + -fx-background-insets: 0, 1 +} diff --git a/src/org/controlsfx/control/table/TableFilter.java b/src/org/controlsfx/control/table/TableFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..f2672cc9d1dfbf71839156ef1c96811801a43d8a --- /dev/null +++ b/src/org/controlsfx/control/table/TableFilter.java @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table; + +import impl.org.controlsfx.table.ColumnFilter; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; + +import java.util.Optional; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import java.util.stream.*; + +/**Applies a filtering control to a provided {@link TableView} instance. + * The filter will be applied immediately on construction, and + * can be made visible by right-clicking the desired column to filter on. + *<br><br> + *<b>Features</b><br> + *-Convenient filter control holds a checklist of distinct items to include/exclude, much like an Excel filter.<br> + *-New/removed records will be captured by the filter control and reflect new or removed values from checklist. + *-Filters on more than one column are combined to only display mutually inclusive records on the client's TableView. + * @param <T> + */ +public final class TableFilter<T> { + + private final TableView<T> tableView; + private final ObservableList<T> backingList; + private final FilteredList<T> filteredList; + + private final ObservableList<ColumnFilter<T,?>> columnFilters = FXCollections.observableArrayList(); + + + /** + * Use TableFilter.forTableView() factory and leverage Builder + */ + @Deprecated + public TableFilter(TableView<T> tableView) { + this(tableView,false); + } + + private TableFilter(TableView<T> tableView, boolean isLazy) { + this.tableView = tableView; + backingList = tableView.getItems(); + filteredList = new FilteredList<>(new SortedList<>(backingList)); + SortedList<T> sortedControlList = new SortedList<>(this.filteredList); + + filteredList.setPredicate(v -> true); + + sortedControlList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedControlList); + + applyForAllColumns(isLazy); + tableView.getStylesheets().add("/impl/org/controlsfx/table/tablefilter.css"); + + if (!isLazy) { + columnFilters.forEach(ColumnFilter::initialize); + } + } + + /** + * Allows specifying a different behavior for the search box on the TableFilter. + * By default, the contains() method on a String is used to evaluate the search box input to qualify the distinct filter values. + * But you can specify a different behavior by providing a simple BiPredicate argument to this method. + * The BiPredicate argument allows you take the input value and target value and use a lambda to evaluate a boolean. + * For instance, you can implement a comparison by assuming the input value is a regular expression, and call matches() + * on the target value to see if it aligns to the pattern. + * @param searchStrategy + */ + public void setSearchStrategy(BiPredicate<String,String> searchStrategy) { + columnFilters.forEach(cf -> cf.setSearchStrategy(searchStrategy)); + } + /** + * Returns the backing {@link ObservableList} originally provided to the constructor. + * @return ObservableList + */ + public ObservableList<T> getBackingList() { + return backingList; + } + /** + * Returns the {@link FilteredList} used by this TableFilter and is backing the {@link TableView}. + * @return FilteredList + */ + public FilteredList<T> getFilteredList() { + return filteredList; + } + /** + * @treatAsPrivate + */ + private void applyForAllColumns(boolean isLazy) { + columnFilters.setAll(tableView.getColumns().stream().flatMap(this::extractNestedColumns) + .map(c -> new ColumnFilter<>(this, c)).collect(Collectors.toList())); + } + private <S> Stream<TableColumn<T,?>> extractNestedColumns(TableColumn<T,S> tableColumn) { + if (tableColumn.getColumns().size() == 0) { + return Stream.of(tableColumn); + } else { + return tableColumn.getColumns().stream().flatMap(this::extractNestedColumns); + } + } + + /** + * Programmatically selects value for the specified TableColumn + */ + public void selectValue(TableColumn<?,?> column, Object value) { + columnFilters.stream().filter(c -> c.getTableColumn() == column) + .forEach(c -> c.selectValue(value)); + } + /** + * Programmatically unselects value for the specified TableColumn + */ + public void unselectValue(TableColumn<?,?> column, Object value) { + columnFilters.stream().filter(c -> c.getTableColumn() == column) + .forEach(c -> c.unselectValue(value)); + } + + /** + * Programmatically selects all values for the specified TableColumn + + */ + public void selectAllValues(TableColumn<?,?> column) { + columnFilters.stream().filter(c -> c.getTableColumn() == column) + .forEach(ColumnFilter::selectAllValues); + } + + /** + * Programmatically unselect all values for the specified TableColumn + */ + public void unSelectAllValues(TableColumn<?,?> column) { + columnFilters.stream().filter(c -> c.getTableColumn() == column) + .forEach(ColumnFilter::unSelectAllValues); + } + public void executeFilter() { + if (columnFilters.stream().filter(ColumnFilter::isFiltered).findAny().isPresent()) { + filteredList.setPredicate(item -> !columnFilters.stream() + .filter(cf -> !cf.evaluate(item)) + .findAny().isPresent()); + } + else { + resetFilter(); + } + } + public void resetFilter() { + filteredList.setPredicate(item -> true); + } + /** + * @treatAsPrivate + */ + public TableView<T> getTableView() { + return tableView; + } + /** + * @treatAsPrivate + */ + public ObservableList<ColumnFilter<T,?>> getColumnFilters() { + return columnFilters; + } + /** + * @treatAsPrivate + */ + public Optional<ColumnFilter<T,?>> getColumnFilter(TableColumn<T,?> tableColumn) { + return columnFilters.stream().filter(f -> f.getTableColumn().equals(tableColumn)).findAny(); + } + public boolean isDirty() { + return columnFilters.stream().filter(ColumnFilter::isFiltered).findAny().isPresent(); + } + + /** + * Returns a TableFilter.Builder to configure a TableFilter on the specified TableView. Call apply() to initialize and return the TableFilter + * @param tableView + * @param <T> + */ + public static <T> Builder<T> forTableView(TableView<T> tableView) { + return new Builder<T>(tableView); + } + + /** + * A Builder for a TableFilter against a specified TableView + * @param <T> + */ + public static final class Builder<T> { + + private final TableView<T> tableView; + private volatile boolean lazyInd = false; + + private Builder(TableView<T> tableView) { + this.tableView = tableView; + } + public Builder<T> lazy(boolean isLazy) { + this.lazyInd = isLazy; + return this; + } + public TableFilter<T> apply() { + return new TableFilter<>(tableView, lazyInd); + } + } + +} diff --git a/src/org/controlsfx/control/table/TableRowExpanderColumn.java b/src/org/controlsfx/control/table/TableRowExpanderColumn.java new file mode 100644 index 0000000000000000000000000000000000000000..c7af39f2a230372552127e32cf5698085d019e82 --- /dev/null +++ b/src/org/controlsfx/control/table/TableRowExpanderColumn.java @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table; + +import impl.org.controlsfx.skin.ExpandableTableRowSkin; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.util.Callback; + +import java.util.HashMap; +import java.util.Map; + +/** + * The TableRowExpanderColumn enables a TableView to provide an expandable editor below each table row. + * The column itself contains a toggle button that on click will show an editor for the current row right below the + * columns. Example: + * + * <pre> + * TableRowExpanderColumn<Customer> expander = new TableRowExpanderColumn<>(param -> { + * HBox editor = new HBox(10); + * TextField text = new TextField(param.getValue().getName()); + * Button save = new Button("Save customer"); + * save.setOnAction(event -> { + * save(); + * param.toggleExpanded(); + * }); + * editor.getChildren().addAll(text, save); + * return editor; + * }); + * + * tableView.getColumns().add(expander); + * </pre> + * + * You can provide a custom cellFactory to customize the toggle button. A typical custom toggle cell implementation + * would look like this: + * + * <pre> + * public class MyCustomToggleCell<S> extends TableCell<S, Boolean> { + * private Button button = new Button(); + * + * public MyCustomToggleCell(TableRowExpanderColumn<S> column) { + * button.setOnAction(event -> column.toggleExpanded(getIndex())); + * } + * + * protected void updateItem(Boolean expanded, boolean empty) { + * super.updateItem(expanded, empty); + * if (expanded == null || empty) { + * setGraphic(null); + * } else { + * button.setText(expanded ? "Collapse" : "Expand"); + * setGraphic(button); + * } + * } + * } + * </pre> + * + * The custom toggle cell utilizes the {@link TableRowExpanderColumn#toggleExpanded(int)} method to toggle + * the row expander instead of param.toggleExpanded() like the editor does. + * + * @param <S> The item type of the TableView + */ +public final class TableRowExpanderColumn<S> extends TableColumn<S, Boolean> { + private static final String STYLE_CLASS = "expander-column"; + private static final String EXPANDER_BUTTON_STYLE_CLASS = "expander-button"; + + private final Map<S, Node> expandedNodeCache = new HashMap<>(); + private final Map<S, BooleanProperty> expansionState = new HashMap<>(); + private Callback<TableRowDataFeatures<S>, Node> expandedNodeCallback; + + /** + * Returns a Boolean property that can be used to manipulate the expanded state for a row + * corresponding to the given item value. + * + * @param item The item corresponding to a table row + * @return The boolean property + */ + public BooleanProperty getExpandedProperty(S item) { + BooleanProperty value = expansionState.get(item); + if (value == null) { + value = new SimpleBooleanProperty(item, "expanded", false) { + /** + * When the expanded state change we refresh the tableview. + * If the expanded state changes to false we remove the cached expanded node. + */ + @Override + protected void invalidated() { + getTableView().refresh(); + if (!getValue()) expandedNodeCache.remove(getBean()); + } + }; + expansionState.put(item, value); + } + return value; + } + + /** + * Get or create and cache the expanded node for a given item. + * + * @param tableRow The table row, used to find the item index + * @return The expanded node for the given item + */ + public Node getOrCreateExpandedNode(TableRow<S> tableRow) { + int index = tableRow.getIndex(); + if (index > -1 && index < getTableView().getItems().size()) { + S item = getTableView().getItems().get(index); + Node node = expandedNodeCache.get(item); + if (node == null) { + node = expandedNodeCallback.call(new TableRowDataFeatures<>(tableRow, this, item)); + expandedNodeCache.put(item, node); + } + return node; + } + return null; + } + + /** + * Return the expanded node for the given item, if it exists. + * + * @param item The item corresponding to a table row + * @return The expanded node, if it exists. + */ + public Node getExpandedNode(S item) { + return expandedNodeCache.get(item); + } + + /** + * Create a row expander column that can be added to the TableView list of columns. + * + * The expandedNodeCallback is expected to return a Node representing the editor that should appear below the + * table row when the toggle button within the expander column is clicked. + * + * Once this column is assigned to a TableView, it will automatically install a custom row factory for the TableView + * so that it can configure a TableRow with the {@link impl.org.controlsfx.skin.ExpandableTableRowSkin}. It is within the skin that the actual + * rendering of the expanded node occurs. + * + * @see TableRowExpanderColumn + * @see TableRowDataFeatures + * + * @param expandedNodeCallback + */ + public TableRowExpanderColumn(Callback<TableRowDataFeatures<S>, Node> expandedNodeCallback) { + this.expandedNodeCallback = expandedNodeCallback; + + getStyleClass().add(STYLE_CLASS); + setCellValueFactory(param -> getExpandedProperty(param.getValue())); + setCellFactory(param -> new ToggleCell()); + installRowFactoryOnTableViewAssignment(); + } + + /** + * Install the row factory on the TableView when this column is assigned to a TableView. + */ + private void installRowFactoryOnTableViewAssignment() { + tableViewProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + getTableView().setRowFactory(param -> new TableRow<S>() { + @Override + protected Skin<?> createDefaultSkin() { + return new ExpandableTableRowSkin<>(this, TableRowExpanderColumn.this); + } + }); + } + }); + } + + /** + * The default toggle cell creates a button with a + or - sign as the text, + * depending on the expanded state of the row it represents. + * + * You can use this as a starting point to implement a custom toggle cell. + */ + private final class ToggleCell extends TableCell<S, Boolean> { + private Button button = new Button(); + + public ToggleCell() { + button.setFocusTraversable(false); + button.getStyleClass().add(EXPANDER_BUTTON_STYLE_CLASS); + button.setPrefSize(16, 16); + button.setPadding(new Insets(0)); + button.setOnAction(event -> toggleExpanded(getIndex())); + } + + @Override + protected void updateItem(Boolean expanded, boolean empty) { + super.updateItem(expanded, empty); + if (expanded == null || empty) { + setGraphic(null); + } else { + button.setText(expanded ? "-" : "+"); + setGraphic(button); + } + } + } + + /** + * Toggle the expanded state of the row at the given index. + * + * @param index The index of the row you want to toggle expansion for. + */ + public void toggleExpanded(int index) { + BooleanProperty expanded = (BooleanProperty) getCellObservableValue(index); + expanded.setValue(!expanded.getValue()); + } + + /** + * This object is passed to the expanded node callback when it is time to create a Node to represent the + * expanded editor of a certain row. The most important method is {@link #getValue()}} which returns the + * object represented by the current row. + * + * Further more, the {@link #expandedProperty()} returns a boolean property indicating the current expansion + * state of the current row. You can use this, or the {@link #toggleExpanded()} method to toggle and inspect + * the expanded state of the row, for example if you want an action inside the row editor to contract the editor. + * + * @param <S> The type of items in the TableView + */ + public static final class TableRowDataFeatures<S> { + private TableRow<S> tableRow; + private TableRowExpanderColumn<S> tableColumn; + private BooleanProperty expandedProperty; + private S value; + + public TableRowDataFeatures(TableRow<S> tableRow, TableRowExpanderColumn<S> tableColumn, S value) { + this.tableRow = tableRow; + this.tableColumn = tableColumn; + this.expandedProperty = (BooleanProperty) tableColumn.getCellObservableValue(tableRow.getIndex()); + this.value = value; + } + + /** + * Return the current TableRow. It is safe to assume that the index returned by {@link TableRow#getIndex()} is + * correct as long as you use it for the initial node creation. It is not safe to trust the result of this call + * at any later time, for example in a button action within the row editor. + * + * @return The current TableRow + */ + public TableRow<S> getTableRow() { + return tableRow; + } + + /** + * Return the TableColumn which contains the toggle button. Normally you would not need to use this directly, + * but rather consult the {@link #expandedProperty()} for inspection and mutation of the toggled state of this row. + * + * @return The TableColumn which contains the toggle button + */ + public TableRowExpanderColumn<S> getTableColumn() { + return tableColumn; + } + + /** + * The expanded property can be used to inspect or mutate the toggled state of this row editor. You can also + * listen for changes to it's state if needed. + * + * @return The expanded property + */ + public BooleanProperty expandedProperty() { + return expandedProperty; + } + + /** + * Toggle the expanded state of this row editor. + */ + public void toggleExpanded() { + BooleanProperty expanded = expandedProperty(); + expanded.setValue(!expanded.getValue()); + } + + /** + * Returns a boolean indicating if the current row is expanded or not + * + * @return A boolean indicating the expanded state of the current editor + */ + public Boolean isExpanded() { + return expandedProperty().getValue(); + } + + /** + * Set the expanded state. This will update the {@link #expandedProperty()} accordingly. + * + * @param expanded Wheter the row editor should be expanded or not + */ + public void setExpanded(Boolean expanded) { + expandedProperty().setValue(expanded); + } + + /** + * The value represented by the current table row. It is important that the value has valid equals/hashCode + * methods, as the row value is used to keep track of the node editor for each row. + * + * @return The value represented by the current table row + */ + public S getValue() { + return value; + } + + } + +} diff --git a/src/org/controlsfx/control/table/TableViewUtils.java b/src/org/controlsfx/control/table/TableViewUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..94a020e3268bd27df729c4180803e6874c65e581 --- /dev/null +++ b/src/org/controlsfx/control/table/TableViewUtils.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table; + +import java.lang.reflect.Field; +import java.util.function.Consumer; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Control; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Skin; +import javafx.scene.control.TableView; +import javafx.scene.control.TreeTableView; + +import com.sun.javafx.scene.control.skin.TableHeaderRow; +import com.sun.javafx.scene.control.skin.TableViewSkin; +import com.sun.javafx.scene.control.skin.TableViewSkinBase; + +/** + * A utility class for API revolving around the JavaFX {@link TableView} and + * {@link TreeTableView} controls. + */ +// not public as not ready for 8.20.7 +final class TableViewUtils { + + /** + * Call this method to be able to programatically manipulate the + * {@link TableView#tableMenuButtonVisibleProperty() TableView menu button} + * (assuming it is visible). This allows developers to, for example, add in + * new {@link MenuItem}. + */ + public static void modifyTableMenu(final TableView<?> tableView, final Consumer<ContextMenu> consumer) { + modifyTableMenu((Control)tableView, consumer); + } + + /** + * Call this method to be able to programatically manipulate the + * {@link TreeTableView#tableMenuButtonVisibleProperty() TreeTableView menu button} + * (assuming it is visible). This allows developers to, for example, add in + * new {@link MenuItem}. + */ + public static void modifyTableMenu(final TreeTableView<?> treeTableView, final Consumer<ContextMenu> consumer) { + modifyTableMenu((Control)treeTableView, consumer); + } + + private static void modifyTableMenu(final Control control, final Consumer<ContextMenu> consumer) { + if (control.getScene() == null) { + control.sceneProperty().addListener(new InvalidationListener() { + @Override public void invalidated(Observable o) { + control.sceneProperty().removeListener(this); + modifyTableMenu(control, consumer); + } + }); + + return; + } + + Skin<?> skin = control.getSkin(); + if (skin == null) { + control.skinProperty().addListener(new InvalidationListener() { + @Override public void invalidated(Observable o) { + control.skinProperty().removeListener(this); + modifyTableMenu(control, consumer); + } + }); + + return; + } + + doModify(skin, consumer); + } + + private static void doModify(Skin<?> skin, Consumer<ContextMenu> consumer) { + if (! (skin instanceof TableViewSkinBase)) return; + + TableViewSkin<?> tableSkin = (TableViewSkin<?>)skin; + TableHeaderRow headerRow = getHeaderRow(tableSkin); + if (headerRow == null) return; + + ContextMenu contextMenu = getContextMenu(headerRow); + consumer.accept(contextMenu); + } + + private static TableHeaderRow getHeaderRow(TableViewSkin<?> tableSkin) { + ObservableList<Node> children = tableSkin.getChildren(); + for (int i = 0, max = children.size(); i < max; i++) { + Node child = children.get(i); + if (child instanceof TableHeaderRow) return (TableHeaderRow) child; + } + return null; + } + + private static ContextMenu getContextMenu(TableHeaderRow headerRow) { + try { + Field privateContextMenuField = TableHeaderRow.class.getDeclaredField("columnPopupMenu"); //$NON-NLS-1$ + privateContextMenuField.setAccessible(true); + ContextMenu contextMenu = (ContextMenu) privateContextMenuField.get(headerRow); + return contextMenu; + } catch (IllegalArgumentException ex) { + ex.printStackTrace(); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } catch (NoSuchFieldException ex) { + ex.printStackTrace(); + } catch (SecurityException ex) { + ex.printStackTrace(); + } + return null; + } +} diff --git a/src/org/controlsfx/control/table/model/JavaFXTableModel.java b/src/org/controlsfx/control/table/model/JavaFXTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..b59512342bf242a60e0d6f00e27333199e9e62fb --- /dev/null +++ b/src/org/controlsfx/control/table/model/JavaFXTableModel.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table.model; + +import javafx.scene.control.TableView; + +/** + * + */ +//not public as not ready for 8.20.7 +interface JavaFXTableModel<T> { + public T getValueAt(int rowIndex, int columnIndex); + + public void setValueAt(T value, int rowIndex, int columnIndex); + + public int getRowCount(); + + public int getColumnCount(); + + public String getColumnName(int columnIndex); + + public void sort(TableView<TableModelRow<T>> table); +} diff --git a/src/org/controlsfx/control/table/model/JavaFXTableModels.java b/src/org/controlsfx/control/table/model/JavaFXTableModels.java new file mode 100644 index 0000000000000000000000000000000000000000..734e123a210f6626ed5d4f58706aec79c3795e8a --- /dev/null +++ b/src/org/controlsfx/control/table/model/JavaFXTableModels.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table.model; + +import java.util.ArrayList; +import java.util.List; + +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.SortType; +import javafx.scene.control.TableView; + +import javax.swing.RowSorter.SortKey; +import javax.swing.SortOrder; +import javax.swing.table.TableModel; +import javax.swing.table.TableRowSorter; + +/** + * + */ +//not public as not ready for 8.20.7 +class JavaFXTableModels { + + /** + * Swing + */ + public static <S> JavaFXTableModel<S> wrap(final TableModel tableModel) { + + return new JavaFXTableModel<S>() { + final TableRowSorter<TableModel> sorter; + + { + sorter = new TableRowSorter<>(tableModel); + } + + @SuppressWarnings("unchecked") + @Override public S getValueAt(int rowIndex, int columnIndex) { + return (S) tableModel.getValueAt(sorter.convertRowIndexToView(rowIndex), columnIndex); + } + + @Override public void setValueAt(S value, int rowIndex, int columnIndex) { + tableModel.setValueAt(value, rowIndex, columnIndex); + } + + @Override public int getRowCount() { + return tableModel.getRowCount(); + } + + @Override public int getColumnCount() { + return tableModel.getColumnCount(); + } + + @Override public String getColumnName(int columnIndex) { + return tableModel.getColumnName(columnIndex); + } + + @Override public void sort(TableView<TableModelRow<S>> table) { + List<SortKey> sortKeys = new ArrayList<>(); + + for (TableColumn<TableModelRow<S>, ?> column : table.getSortOrder()) { + final int columnIndex = table.getVisibleLeafIndex(column); + final SortType sortType = column.getSortType(); + SortOrder sortOrder = sortType == SortType.ASCENDING ? SortOrder.ASCENDING : + sortType == SortType.DESCENDING ? SortOrder.DESCENDING : + SortOrder.UNSORTED; + SortKey sortKey = new SortKey(columnIndex, sortOrder); + sortKeys.add(sortKey); + + sorter.setComparator(columnIndex, column.getComparator()); + } + + sorter.setSortKeys(sortKeys); + sorter.sort(); + } + }; + } +} diff --git a/src/org/controlsfx/control/table/model/TableModelRow.java b/src/org/controlsfx/control/table/model/TableModelRow.java new file mode 100644 index 0000000000000000000000000000000000000000..0eaa638e19180ef5cfe3fc001f893cc2e51fa9f1 --- /dev/null +++ b/src/org/controlsfx/control/table/model/TableModelRow.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table.model; + +/** + */ +class TableModelRow<S> { + private final int columnCount; + private final JavaFXTableModel<S> tableModel; + private final int row; + + TableModelRow(JavaFXTableModel<S> tableModel, int row) { + this.row = row; + this.tableModel = tableModel; + this.columnCount = tableModel.getColumnCount(); + } + + public Object get(int column) { + return column < 0 || column >= columnCount ? null : this.tableModel.getValueAt(row, column); + } + + @Override public String toString() { + String text = "Row " + row + ": [ "; //$NON-NLS-1$ //$NON-NLS-2$ + + for (int col = 0; col < columnCount; col++) { + text += get(col); + + if (col < (columnCount - 1)) { + text += ", "; //$NON-NLS-1$ + } + } + + text += " ]"; //$NON-NLS-1$ + return text; + } +} diff --git a/src/org/controlsfx/control/table/model/TableModelTableView.java b/src/org/controlsfx/control/table/model/TableModelTableView.java new file mode 100644 index 0000000000000000000000000000000000000000..596a8d5c03750732b9d829566db25e19833353ca --- /dev/null +++ b/src/org/controlsfx/control/table/model/TableModelTableView.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table.model; + +import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; + +/** + * + */ +//not public as not ready for 8.20.7 +class TableModelTableView<S> extends TableView<TableModelRow<S>> { + + public TableModelTableView(final JavaFXTableModel<S> tableModel) { + // create a dummy items list of the appropriate size, where the returned + // value is the index of the row + setItems(new ReadOnlyUnbackedObservableList<TableModelRow<S>>() { + @Override public TableModelRow<S> get(int row) { + if (row < 0 || row >= tableModel.getRowCount()) return null; + TableModelRow<S> backingRow = new TableModelRow<>(tableModel, row); + return backingRow; + } + + @Override public int size() { + return tableModel.getRowCount(); + } + }); + + setSortPolicy(table -> { + tableModel.sort(table); + return true; + }); + + // create columns from the table model + for (int i = 0; i < tableModel.getColumnCount(); i++) { + TableColumn<TableModelRow<S>,?> column = new TableColumn<>(tableModel.getColumnName(i)); + column.setCellValueFactory(new TableModelValueFactory<>(tableModel, i)); + getColumns().add(column); + } + } +} diff --git a/src/org/controlsfx/control/table/model/TableModelValueFactory.java b/src/org/controlsfx/control/table/model/TableModelValueFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..bc098b93cbe902d011bee7ee030ac72ec1365421 --- /dev/null +++ b/src/org/controlsfx/control/table/model/TableModelValueFactory.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.table.model; + +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.TableColumn; +import javafx.util.Callback; +import javafx.scene.control.TableColumn.CellDataFeatures; + +/** + * + */ +//not public as not ready for 8.20.7 +class TableModelValueFactory<S, T> implements Callback<CellDataFeatures<TableModelRow<S>, T>, ObservableValue<T>> { + @SuppressWarnings("unused") + private final JavaFXTableModel<S> _tableModel; + private final int _columnIndex; + + public TableModelValueFactory(JavaFXTableModel<S> tableModel, int columnIndex) { + _tableModel = tableModel; + _columnIndex = columnIndex; + } + + @SuppressWarnings("unchecked") + @Override public ObservableValue<T> call(TableColumn.CellDataFeatures<TableModelRow<S>, T> cdf) { + TableModelRow<S> row = cdf.getValue(); + T valueAt = (T) row.get(_columnIndex); + return valueAt instanceof ObservableValue ? ((ObservableValue<T>) valueAt) : new ReadOnlyObjectWrapper<>(valueAt); + } +} diff --git a/src/org/controlsfx/control/taskprogressview.css b/src/org/controlsfx/control/taskprogressview.css new file mode 100644 index 0000000000000000000000000000000000000000..2c88aba4dec6cdddac7b8cf2e4307b293475d3f8 --- /dev/null +++ b/src/org/controlsfx/control/taskprogressview.css @@ -0,0 +1,53 @@ + +.task-progress-view { + -fx-background-color: white; +} + +.task-progress-view > * > .label { + -fx-text-fill: gray; + -fx-font-size: 18.0; + -fx-alignment: center; + -fx-padding: 10.0 0.0 5.0 0.0; +} + +.task-progress-view > * > .list-view { + -fx-border-color: transparent; + -fx-background-color: transparent; +} + +.task-title { + -fx-font-weight: bold; +} + +.task-progress-bar .bar { + -fx-padding: 6px; + -fx-background-radius: 0; + -fx-border-radius: 0; +} + +.task-progress-bar .track { + -fx-background-radius: 0; +} + +.task-message { +} + +.task-list-cell { + -fx-background-color: transparent; + -fx-padding: 4 10 8 10; + -fx-border-color: transparent transparent linear-gradient(from 0.0% 0.0% to 100.0% 100.0%, transparent, rgba(0.0,0.0,0.0,0.2), transparent) transparent; +} + +.task-list-cell-empty { + -fx-background-color: transparent; + -fx-border-color: transparent; +} + +.task-cancel-button { + -fx-base-color: red; + -fx-font-size: .75em; + -fx-font-weight: bold; + -fx-padding: 4px; + -fx-border-radius: 0; + -fx-background-radius: 0; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/textfield/AutoCompletionBinding.java b/src/org/controlsfx/control/textfield/AutoCompletionBinding.java new file mode 100644 index 0000000000000000000000000000000000000000..f851ea07d57efe8013e9b6ae37fb6ddb636240a1 --- /dev/null +++ b/src/org/controlsfx/control/textfield/AutoCompletionBinding.java @@ -0,0 +1,558 @@ +/** + * Copyright (c) 2014, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.textfield; + +import com.sun.javafx.event.EventHandlerManager; +import impl.org.controlsfx.skin.AutoCompletePopup; +import impl.org.controlsfx.skin.AutoCompletePopupSkin; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.concurrent.Task; +import javafx.event.*; +import javafx.scene.Node; +import javafx.scene.control.ListView; +import javafx.scene.control.Skin; +import javafx.util.Callback; +import javafx.util.StringConverter; + +import java.util.Collection; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.IntegerProperty; + +/** + * The AutoCompletionBinding is the abstract base class of all auto-completion bindings. + * This class is the core logic for the auto-completion feature but highly customizable. + * + * <p>To use the autocompletion functionality, refer to the {@link TextFields} class. + * + * The popup size can be modified through its {@link #setVisibleRowCount(int) } + * for the height and all the usual methods for the width. + * + * @param <T> Model-Type of the suggestions + * @see TextFields + */ +public abstract class AutoCompletionBinding<T> implements EventTarget { + + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + private final Node completionTarget; + private final AutoCompletePopup<T> autoCompletionPopup; + private final Object suggestionsTaskLock = new Object(); + + private FetchSuggestionsTask suggestionsTask = null; + private Callback<ISuggestionRequest, Collection<T>> suggestionProvider = null; + private boolean ignoreInputChanges = false; + private long delay = 250; + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + + /** + * Creates a new AutoCompletionBinding + * + * @param completionTarget The target node to which auto-completion shall be added + * @param suggestionProvider The strategy to retrieve suggestions + * @param converter The converter to be used to convert suggestions to strings + */ + protected AutoCompletionBinding(Node completionTarget, + Callback<ISuggestionRequest, Collection<T>> suggestionProvider, + StringConverter<T> converter){ + + this.completionTarget = completionTarget; + this.suggestionProvider = suggestionProvider; + this.autoCompletionPopup = new AutoCompletePopup<>(); + this.autoCompletionPopup.setConverter(converter); + + autoCompletionPopup.setOnSuggestion(sce -> { + try{ + setIgnoreInputChanges(true); + completeUserInput(sce.getSuggestion()); + fireAutoCompletion(sce.getSuggestion()); + hidePopup(); + }finally{ + // Ensure that ignore is always set back to false + setIgnoreInputChanges(false); + } + }); + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** + * Specifies whether the PopupWindow should be hidden when an unhandled + * escape key is pressed while the popup has focus. + * + * @param value + */ + public void setHideOnEscape(boolean value) { + autoCompletionPopup.setHideOnEscape(value); + } + + /** + * Set the current text the user has entered + * @param userText + */ + public final void setUserInput(String userText){ + if(!isIgnoreInputChanges()){ + onUserInputChanged(userText); + } + } + + /** + * Sets the delay in ms between a key press and the suggestion popup being displayed. + * + * @param delay + */ + public final void setDelay(long delay) { + this.delay = delay; + } + + /** + * Gets the target node for auto completion + * @return the target node for auto completion + */ + public Node getCompletionTarget(){ + return completionTarget; + } + + /** + * Disposes the binding. + */ + public abstract void dispose(); + + + /** + * Set the maximum number of rows to be visible in the popup when it is + * showing. + * + * @param value + */ + public final void setVisibleRowCount(int value) { + autoCompletionPopup.setVisibleRowCount(value); + } + + /** + * Return the maximum number of rows to be visible in the popup when it is + * showing. + * + * @return the maximum number of rows to be visible in the popup when it is + * showing. + */ + public final int getVisibleRowCount() { + return autoCompletionPopup.getVisibleRowCount(); + } + + /** + * Return an property representing the maximum number of rows to be visible + * in the popup when it is showing. + * + * @return an property representing the maximum number of rows to be visible + * in the popup when it is showing. + */ + public final IntegerProperty visibleRowCountProperty() { + return autoCompletionPopup.visibleRowCountProperty(); + } + + /** + * Sets the prefWidth of the popup. + * + * @param value + */ + public final void setPrefWidth(double value) { + autoCompletionPopup.setPrefWidth(value); + } + + /** + * Return the pref width of the popup. + * + * @return the pref width of the popup. + */ + public final double getPrefWidth() { + return autoCompletionPopup.getPrefWidth(); + } + + /** + * Return the property associated with the pref width. + * @return + */ + public final DoubleProperty prefWidthProperty() { + return autoCompletionPopup.prefWidthProperty(); + } + + /** + * Sets the minWidth of the popup. + * + * @param value + */ + public final void setMinWidth(double value) { + autoCompletionPopup.setMinWidth(value); + } + + /** + * Return the min width of the popup. + * + * @return the min width of the popup. + */ + public final double getMinWidth() { + return autoCompletionPopup.getMinWidth(); + } + + /** + * Return the property associated with the min width. + * @return + */ + public final DoubleProperty minWidthProperty() { + return autoCompletionPopup.minWidthProperty(); + } + + /** + * Sets the maxWidth of the popup. + * + * @param value + */ + public final void setMaxWidth(double value) { + autoCompletionPopup.setMaxWidth(value); + } + + /** + * Return the max width of the popup. + * + * @return the max width of the popup. + */ + public final double getMaxWidth() { + return autoCompletionPopup.getMaxWidth(); + } + + /** + * Return the property associated with the max width. + * @return + */ + public final DoubleProperty maxWidthProperty() { + return autoCompletionPopup.maxWidthProperty(); + } + + /*************************************************************************** + * * + * Protected methods * + * * + **************************************************************************/ + + /** + * Complete the current user-input with the provided completion. + * Sub-classes have to provide a concrete implementation. + * @param completion + */ + protected abstract void completeUserInput(T completion); + + + /** + * Show the auto completion popup + */ + protected void showPopup(){ + autoCompletionPopup.show(completionTarget); + selectFirstSuggestion(autoCompletionPopup); + } + + /** + * Hide the auto completion targets + */ + protected void hidePopup(){ + autoCompletionPopup.hide(); + } + + protected void fireAutoCompletion(T completion){ + Event.fireEvent(this, new AutoCompletionEvent<>(completion)); + } + + + /*************************************************************************** + * * + * Private methods * + * * + **************************************************************************/ + + /** + * Selects the first suggestion (if any), so the user can choose it + * by pressing enter immediately. + */ + private void selectFirstSuggestion(AutoCompletePopup<?> autoCompletionPopup){ + Skin<?> skin = autoCompletionPopup.getSkin(); + if(skin instanceof AutoCompletePopupSkin){ + AutoCompletePopupSkin<?> au = (AutoCompletePopupSkin<?>)skin; + ListView<?> li = (ListView<?>)au.getNode(); + if(li.getItems() != null && !li.getItems().isEmpty()){ + li.getSelectionModel().select(0); + } + } + } + + /** + * Occurs when the user text has changed and the suggestions require an update + * @param userText + */ + private final void onUserInputChanged(final String userText){ + synchronized (suggestionsTaskLock) { + if(suggestionsTask != null && suggestionsTask.isRunning()){ + // cancel the current running task + suggestionsTask.cancel(); + } + // create a new fetcher task + suggestionsTask = new FetchSuggestionsTask(userText, delay); + new Thread(suggestionsTask).start(); + } + } + + /** + * Shall changes to the user input be ignored? + * @return + */ + private boolean isIgnoreInputChanges(){ + return ignoreInputChanges; + } + + /** + * If IgnoreInputChanges is set to true, all changes to the user input are + * ignored. This is primary used to avoid self triggering while + * auto completing. + * @param state + */ + private void setIgnoreInputChanges(boolean state){ + ignoreInputChanges = state; + } + + /*************************************************************************** + * * + * Inner classes and interfaces * + * * + **************************************************************************/ + + + /** + * Represents a suggestion fetch request + * + */ + public static interface ISuggestionRequest { + /** + * Is this request canceled? + * @return {@code true} if the request is canceled, otherwise {@code false} + */ + public boolean isCancelled(); + + /** + * Get the user text to which suggestions shall be found + * @return {@link String} containing the user text + */ + public String getUserText(); + } + + + + /** + * This task is responsible to fetch suggestions asynchronous + * by using the current defined suggestionProvider + * + */ + private class FetchSuggestionsTask extends Task<Void> implements ISuggestionRequest { + private final String userText; + private final long delay; + + public FetchSuggestionsTask(String userText, long delay){ + this.userText = userText; + this.delay = delay; + } + + @Override + protected Void call() throws Exception { + Callback<ISuggestionRequest, Collection<T>> provider = suggestionProvider; + if(provider != null){ + long startTime = System.currentTimeMillis(); + final Collection<T> fetchedSuggestions = provider.call(this); + long sleepTime = startTime + delay - System.currentTimeMillis(); + if (sleepTime > 0 && !isCancelled()) { + Thread.sleep(sleepTime); + } + if(!isCancelled()){ + Platform.runLater(() -> { + if(fetchedSuggestions != null && !fetchedSuggestions.isEmpty()){ + autoCompletionPopup.getSuggestions().setAll(fetchedSuggestions); + showPopup(); + }else{ + // No suggestions found, so hide the popup + hidePopup(); + } + }); + } + }else { + // No suggestion provider + hidePopup(); + } + return null; + } + + @Override + public String getUserText() { + return userText; + } + } + + /*************************************************************************** + * * + * Events * + * * + **************************************************************************/ + + + // --- AutoCompletionEvent + + /** + * Represents an Event which is fired after an auto completion. + */ + @SuppressWarnings("serial") + public static class AutoCompletionEvent<TE> extends Event { + + /** + * The event type that should be listened to by people interested in + * knowing when an auto completion has been performed. + */ + @SuppressWarnings("rawtypes") + public static final EventType<AutoCompletionEvent> AUTO_COMPLETED = new EventType<>("AUTO_COMPLETED"); //$NON-NLS-1$ + + private final TE completion; + + /** + * Creates a new event that can subsequently be fired. + */ + public AutoCompletionEvent(TE completion) { + super(AUTO_COMPLETED); + this.completion = completion; + } + + /** + * Returns the chosen completion. + */ + public TE getCompletion() { + return completion; + } + } + + + private ObjectProperty<EventHandler<AutoCompletionEvent<T>>> onAutoCompleted; + + /** + * Set a event handler which is invoked after an auto completion. + * @param value + */ + public final void setOnAutoCompleted(EventHandler<AutoCompletionEvent<T>> value) { + onAutoCompletedProperty().set( value); + } + + public final EventHandler<AutoCompletionEvent<T>> getOnAutoCompleted() { + return onAutoCompleted == null ? null : onAutoCompleted.get(); + } + + public final ObjectProperty<EventHandler<AutoCompletionEvent<T>>> onAutoCompletedProperty() { + if (onAutoCompleted == null) { + onAutoCompleted = new ObjectPropertyBase<EventHandler<AutoCompletionEvent<T>>>() { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override protected void invalidated() { + eventHandlerManager.setEventHandler( + AutoCompletionEvent.AUTO_COMPLETED, + (EventHandler<AutoCompletionEvent>)(Object)get()); + } + + @Override + public Object getBean() { + return AutoCompletionBinding.this; + } + + @Override + public String getName() { + return "onAutoCompleted"; //$NON-NLS-1$ + } + }; + } + return onAutoCompleted; + } + + + /*************************************************************************** + * * + * EventTarget Implementation * + * * + **************************************************************************/ + + final EventHandlerManager eventHandlerManager = new EventHandlerManager(this); + + /** + * Registers an event handler to this EventTarget. The handler is called when the + * menu item receives an {@code Event} of the specified type during the bubbling + * phase of event delivery. + * + * @param <E> the specific event class of the handler + * @param eventType the type of the events to receive by the handler + * @param eventHandler the handler to register + * @throws NullPointerException if the event type or handler is null + */ + public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { + eventHandlerManager.addEventHandler(eventType, eventHandler); + } + + /** + * Unregisters a previously registered event handler from this EventTarget. One + * handler might have been registered for different event types, so the + * caller needs to specify the particular event type from which to + * unregister the handler. + * + * @param <E> the specific event class of the handler + * @param eventType the event type from which to unregister + * @param eventHandler the handler to unregister + * @throws NullPointerException if the event type or handler is null + */ + public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { + eventHandlerManager.removeEventHandler(eventType, eventHandler); + } + + /** {@inheritDoc} */ + @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + return tail.prepend(eventHandlerManager); + } + + +} diff --git a/src/org/controlsfx/control/textfield/CustomPasswordField.java b/src/org/controlsfx/control/textfield/CustomPasswordField.java new file mode 100644 index 0000000000000000000000000000000000000000..0265dd6f77f8a6dcfe319981ce0131fbfe8476cc --- /dev/null +++ b/src/org/controlsfx/control/textfield/CustomPasswordField.java @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.textfield; + +import impl.org.controlsfx.skin.CustomTextFieldSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Node; +import javafx.scene.control.PasswordField; +import javafx.scene.control.Skin; + +/** + * A base class for people wanting to customize a {@link PasswordField} to contain nodes + * inside the input field area itself, without being on top of the users typed-in text. + * + * <p>Whilst not exactly the same, refer to the {@link CustomTextField} javadoc + * for a screenshot and more detail. The obvious difference is that of course + * the CustomPasswordField masks the input from users, but in all other ways + * is equivalent to {@link CustomTextField}. + * + * @see CustomPasswordField + * @see TextFields + */ +public class CustomPasswordField extends PasswordField { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Instantiates a default CustomPasswordField. + */ + public CustomPasswordField() { + getStyleClass().addAll("custom-text-field", "custom-password-field"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- left + private ObjectProperty<Node> left = new SimpleObjectProperty<>(this, "left"); //$NON-NLS-1$ + + /** + * Property representing the {@link Node} that is placed on the left of + * the password field. + * @return An ObjectProperty. + */ + public final ObjectProperty<Node> leftProperty() { + return left; + } + + /** + * + * @return The {@link Node} that is placed on the left of + * the password field. + */ + public final Node getLeft() { + return left.get(); + } + + /** + * Sets the {@link Node} that is placed on the left of + * the password field. + * @param value + */ + public final void setLeft(Node value) { + left.set(value); + } + + + // --- right + private ObjectProperty<Node> right = new SimpleObjectProperty<>(this, "right"); //$NON-NLS-1$ + + /** + * Property representing the {@link Node} that is placed on the right of + * the password field. + * @return An ObjectProperty. + */ + public final ObjectProperty<Node> rightProperty() { + return right; + } + + /** + * + * @return The {@link Node} that is placed on the right of + * the password field. + */ + public final Node getRight() { + return right.get(); + } + + /** + * Sets the {@link Node} that is placed on the right of + * the password field. + * @param value + */ + public final void setRight(Node value) { + right.set(value); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new CustomTextFieldSkin(this) { + @Override public ObjectProperty<Node> leftProperty() { + return CustomPasswordField.this.leftProperty(); + } + + @Override public ObjectProperty<Node> rightProperty() { + return CustomPasswordField.this.rightProperty(); + } + }; + } + + /** + * {@inheritDoc} + */ + @Override public String getUserAgentStylesheet() { + return CustomTextField.class.getResource("customtextfield.css").toExternalForm(); //$NON-NLS-1$ + } + +} diff --git a/src/org/controlsfx/control/textfield/CustomTextField.java b/src/org/controlsfx/control/textfield/CustomTextField.java new file mode 100644 index 0000000000000000000000000000000000000000..5eb1ce64b719e14a46de18b2e4527d177153e98c --- /dev/null +++ b/src/org/controlsfx/control/textfield/CustomTextField.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.textfield; + +import impl.org.controlsfx.skin.CustomTextFieldSkin; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Node; +import javafx.scene.control.Skin; +import javafx.scene.control.TextField; + +/** + * A base class for people wanting to customize a {@link TextField} to contain nodes + * inside the text field itself, without being on top of the users typed-in text. + * + * <h3>Screenshot</h3> + * <p>The following screenshot is taken from the HelloControlsFX sample application, + * and shows a normal TextField, with a {@link TextFields#createClearableTextField() clearable text field}, + * followed by three CustomTextFields. Note what happens with long text input - + * it is prevented from going beneath the left and right graphics. Of course, if + * the keyboard caret moves to the right, the text will become visible, but this + * is because it will all scroll to the left (as is the case in a normal {@link TextField}). + * + * <br> + * <center> + * <img src="customTextField.png" alt="Screenshot of CustomTextField"> + * </center> + * + * @see TextFields + * @see CustomPasswordField + */ +public class CustomTextField extends TextField { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Instantiates a default CustomTextField. + */ + public CustomTextField() { + getStyleClass().add("custom-text-field"); //$NON-NLS-1$ + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- left + private ObjectProperty<Node> left = new SimpleObjectProperty<>(this, "left"); //$NON-NLS-1$ + + /** + * + * @return An ObjectProperty wrapping the {@link Node} that is placed + * on the left ofthe text field. + */ + public final ObjectProperty<Node> leftProperty() { + return left; + } + + /** + * + * @return the {@link Node} that is placed on the left of + * the text field. + */ + public final Node getLeft() { + return left.get(); + } + + /** + * Sets the {@link Node} that is placed on the left of + * the text field. + * @param value + */ + public final void setLeft(Node value) { + left.set(value); + } + + + // --- right + private ObjectProperty<Node> right = new SimpleObjectProperty<>(this, "right"); //$NON-NLS-1$ + + /** + * Property representing the {@link Node} that is placed on the right of + * the text field. + * @return An ObjectProperty. + */ + public final ObjectProperty<Node> rightProperty() { + return right; + } + + /** + * + * @return The {@link Node} that is placed on the right of + * the text field. + */ + public final Node getRight() { + return right.get(); + } + + /** + * Sets the {@link Node} that is placed on the right of + * the text field. + * @param value + */ + public final void setRight(Node value) { + right.set(value); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * {@inheritDoc} + */ + @Override protected Skin<?> createDefaultSkin() { + return new CustomTextFieldSkin(this) { + @Override public ObjectProperty<Node> leftProperty() { + return CustomTextField.this.leftProperty(); + } + + @Override public ObjectProperty<Node> rightProperty() { + return CustomTextField.this.rightProperty(); + } + }; + } + + /** + * {@inheritDoc} + */ + @Override public String getUserAgentStylesheet() { + return CustomTextField.class.getResource("customtextfield.css").toExternalForm(); //$NON-NLS-1$ + } +} diff --git a/src/org/controlsfx/control/textfield/TextFields.java b/src/org/controlsfx/control/textfield/TextFields.java new file mode 100644 index 0000000000000000000000000000000000000000..3f8d7ab4dc2fbb597286b2833f81e34f1e60b839 --- /dev/null +++ b/src/org/controlsfx/control/textfield/TextFields.java @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.control.textfield; + +import impl.org.controlsfx.autocompletion.AutoCompletionTextFieldBinding; +import impl.org.controlsfx.autocompletion.SuggestionProvider; + +import java.util.Arrays; +import java.util.Collection; + +import javafx.animation.FadeTransition; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.ObjectProperty; +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.util.Callback; +import javafx.util.Duration; +import javafx.util.StringConverter; + +import org.controlsfx.control.textfield.AutoCompletionBinding.ISuggestionRequest; + +/** + * A class containing useful customizations for the JavaFX {@link TextField}. + * Note that this class is experimental and the API may change in future + * releases. Note also that this class makes use of the {@link CustomTextField} + * class. + * + * @see CustomTextField + */ +public class TextFields { + private static final Duration FADE_DURATION = Duration.millis(350); + + private TextFields() { + // no-op + } + + /*************************************************************************** + * * + * Search fields * + * * + **************************************************************************/ + + /** + * Creates a TextField that shows a clear button inside the TextField (on + * the right hand side of it) when text is entered by the user. + */ + public static TextField createClearableTextField() { + CustomTextField inputField = new CustomTextField(); + setupClearButtonField(inputField, inputField.rightProperty()); + return inputField; + } + + /** + * Creates a PasswordField that shows a clear button inside the PasswordField + * (on the right hand side of it) when text is entered by the user. + */ + public static PasswordField createClearablePasswordField() { + CustomPasswordField inputField = new CustomPasswordField(); + setupClearButtonField(inputField, inputField.rightProperty()); + return inputField; + } + + private static void setupClearButtonField(TextField inputField, ObjectProperty<Node> rightProperty) { + inputField.getStyleClass().add("clearable-field"); //$NON-NLS-1$ + + Region clearButton = new Region(); + clearButton.getStyleClass().addAll("graphic"); //$NON-NLS-1$ + StackPane clearButtonPane = new StackPane(clearButton); + clearButtonPane.getStyleClass().addAll("clear-button"); //$NON-NLS-1$ + clearButtonPane.setOpacity(0.0); + clearButtonPane.setCursor(Cursor.DEFAULT); + clearButtonPane.setOnMouseReleased(e -> inputField.clear()); + clearButtonPane.managedProperty().bind(inputField.editableProperty()); + clearButtonPane.visibleProperty().bind(inputField.editableProperty()); + + rightProperty.set(clearButtonPane); + + final FadeTransition fader = new FadeTransition(FADE_DURATION, clearButtonPane); + fader.setCycleCount(1); + + inputField.textProperty().addListener(new InvalidationListener() { + @Override public void invalidated(Observable arg0) { + String text = inputField.getText(); + boolean isTextEmpty = text == null || text.isEmpty(); + boolean isButtonVisible = fader.getNode().getOpacity() > 0; + + if (isTextEmpty && isButtonVisible) { + setButtonVisible(false); + } else if (!isTextEmpty && !isButtonVisible) { + setButtonVisible(true); + } + } + + private void setButtonVisible( boolean visible ) { + fader.setFromValue(visible? 0.0: 1.0); + fader.setToValue(visible? 1.0: 0.0); + fader.play(); + } + }); + } + + /*************************************************************************** + * * + * Auto-completion * + * * + **************************************************************************/ + + /** + * Create a new auto-completion binding between the given textField and the + * given suggestion provider. + * + * The {@link TextFields} API has some suggestion-provider builder methods + * for simple use cases. + * + * @param textField The {@link TextField} to which auto-completion shall be added + * @param suggestionProvider A suggestion-provider strategy to use + * @param converter The converter to be used to convert suggestions to strings + */ + public static <T> AutoCompletionBinding<T> bindAutoCompletion(TextField textField, + Callback<ISuggestionRequest, Collection<T>> suggestionProvider, + StringConverter<T> converter) { + return new AutoCompletionTextFieldBinding<>(textField, + suggestionProvider, converter); + } + + /** + * Create a new auto-completion binding between the given textField and the + * given suggestion provider. + * + * The {@link TextFields} API has some suggestion-provider builder methods + * for simple use cases. + * + * @param textField The {@link TextField} to which auto-completion shall be added + * @param suggestionProvider A suggestion-provider strategy to use + * @return The AutoCompletionBinding + */ + public static <T> AutoCompletionBinding<T> bindAutoCompletion(TextField textField, + Callback<ISuggestionRequest, Collection<T>> suggestionProvider){ + return new AutoCompletionTextFieldBinding<>(textField, suggestionProvider); + } + + /** + * Create a new auto-completion binding between the given {@link TextField} + * using the given auto-complete suggestions + * + * @param textField The {@link TextField} to which auto-completion shall be added + * @param possibleSuggestions Possible auto-complete suggestions + * @return The AutoCompletionBinding + */ + public static <T> AutoCompletionBinding<T> bindAutoCompletion( + TextField textField, @SuppressWarnings("unchecked") T... possibleSuggestions) { + return bindAutoCompletion(textField, Arrays.asList(possibleSuggestions)); + } + + public static <T> AutoCompletionBinding<T> bindAutoCompletion( + TextField textField, Collection<T> possibleSuggestions) { + return new AutoCompletionTextFieldBinding<>(textField, + SuggestionProvider.create(possibleSuggestions)); + } +} + diff --git a/src/org/controlsfx/control/textfield/autocompletion.css b/src/org/controlsfx/control/textfield/autocompletion.css new file mode 100644 index 0000000000000000000000000000000000000000..2e22806d3ad8af001b5224476664f7ddd4129c1a --- /dev/null +++ b/src/org/controlsfx/control/textfield/autocompletion.css @@ -0,0 +1,31 @@ +/** + * Style based on Modena.css combo-box-popup style + */ + +.auto-complete-popup > .list-view { + -fx-background-color: + linear-gradient(to bottom, + derive(-fx-color,-17%), + derive(-fx-color,-30%) + ), + -fx-control-inner-background; + -fx-background-insets: -1 -2 -1 -1, 0 -1 0 0; + -fx-effect: dropshadow( gaussian , rgba(0,0,0,0.2) , 12, 0.0 , 0 , 8 ); +} +.auto-complete-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell { + -fx-padding: 4 0 4 5; + /* No alternate highlighting */ + -fx-background: -fx-control-inner-background; +} +.auto-complete-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected { + -fx-background: -fx-selection-bar-non-focused; + -fx-background-color: -fx-background; +} +.auto-complete-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:hover, +.auto-complete-popup > .list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected:hover { + -fx-background: -fx-accent; + -fx-background-color: -fx-selection-bar; +} +.auto-complete-popup > .list-view > .placeholder > .label { + -fx-text-fill: derive(-fx-control-inner-background,-30%); +} \ No newline at end of file diff --git a/src/org/controlsfx/control/textfield/customtextfield.css b/src/org/controlsfx/control/textfield/customtextfield.css new file mode 100644 index 0000000000000000000000000000000000000000..f8e08cfa60abb704442cff3ffb4f01085e944b5f --- /dev/null +++ b/src/org/controlsfx/control/textfield/customtextfield.css @@ -0,0 +1,89 @@ +/************************************************************************** + * + * CustomTextField + * + **************************************************************************/ + +.custom-text-field { + -fx-text-fill: -fx-text-inner-color; + -fx-highlight-fill: derive(-fx-control-inner-background,-20%); + -fx-highlight-text-fill: -fx-text-inner-color; + -fx-prompt-text-fill: derive(-fx-control-inner-background,-30%); + -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border), + linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background); + -fx-background-insets: 0, 1; + -fx-background-radius: 3, 2; + +} + +/* +.custom-text-field { + -fx-background-color: null; + -fx-background-insets: 0; +} +*/ +.custom-text-field:no-side-nodes { + -fx-padding: 0.333333em 0.583em 0.333333em 0.583em; +} + +.custom-text-field:left-node-visible { + -fx-padding: 0.333333em 0.583em 0.333333em 0; +} + +.custom-text-field:right-node-visible { + -fx-padding: 0.333333em 0 0.333333em 0.583em; +} + +.custom-text-field:left-node-visible:right-node-visible { + -fx-padding: 0.333333em 0 0.333333em 0; +} + +.custom-text-field:left-node-visible .left-pane { + -fx-padding: 0 3 0 3; +} + +.custom-text-field:right-node-visible .right-pane { + -fx-padding: 0 3 0 3; +} + +.custom-text-field:focused, +.custom-text-field:text-field-has-focus { + -fx-highlight-fill: -fx-accent; + -fx-highlight-text-fill: white; + -fx-background-color: + -fx-focus-color, + -fx-control-inner-background, + -fx-faint-focus-color, + linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background); + -fx-background-insets: -0.2, 1, -1.4, 3; + -fx-background-radius: 3, 2, 4, 0; + -fx-prompt-text-fill: transparent; +} + + + + +/************************************************************************** + * + * Clearable Text / Password Field + * + **************************************************************************/ + +.clearable-field .clear-button { + -fx-padding: 0 3 0 0; +} + +.clearable-field .clear-button > .graphic { + -fx-background-color: #949494; + -fx-scale-shape: false; + -fx-padding: 4.5 4.5 4.5 4.5; /* Graphic is 9x9 px */ + -fx-shape: "M395.992,296.758l1.794-1.794l7.292,7.292l-1.795,1.794 L395.992,296.758z M403.256,294.992l1.794,1.794l-7.292,7.292l-1.794-1.795 L403.256,294.992z"; +} + +.clearable-field .clear-button:hover > .graphic { + -fx-background-color: #ee4444; +} + +.clearable-field .clear-button:pressed > .graphic { + -fx-background-color: #ff1111; +} \ No newline at end of file diff --git a/src/org/controlsfx/control/textfield/package-info.java b/src/org/controlsfx/control/textfield/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f7985d8c3c11c791a67f380bd1a00bfe84f587fd --- /dev/null +++ b/src/org/controlsfx/control/textfield/package-info.java @@ -0,0 +1,4 @@ +/** + * A package containing a number of useful classes related to text input. + */ +package org.controlsfx.control.textfield; \ No newline at end of file diff --git a/src/org/controlsfx/control/toggleswitch.css b/src/org/controlsfx/control/toggleswitch.css new file mode 100644 index 0000000000000000000000000000000000000000..022de66131f1bd5bb6e14179d395e2477df126d6 --- /dev/null +++ b/src/org/controlsfx/control/toggleswitch.css @@ -0,0 +1,51 @@ +/******************************************************************************* + * * + * ToggleSwitch * + * * + ******************************************************************************/ +.toggle-switch{ + -thumb-move-animation-time: 200; +} + +.toggle-switch .text { + -fx-font-size: 1em; + -fx-text-fill: -fx-text-base-color; +} + +.toggle-switch .thumb { + -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -20%), derive(-fx-text-box-border, -30%)), + -fx-inner-border, + -fx-body-color; + -fx-background-insets: 0, 1, 2; + -fx-background-radius: 1.0em; /* large value to make sure this remains circular */ + -fx-padding: 0.75em; + -fx-alignment: CENTER; + -fx-content-display: LEFT; +} + +.toggle-switch .thumb-area{ + -fx-background-radius: 1em; + -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -20%), derive(-fx-text-box-border, -30%)), #f5f5f5; + -fx-background-insets: 0, 1; +} + +.toggle-switch:hover .thumb{ + -fx-color: -fx-hover-base +} + +.toggle-switch:selected .thumb-area{ + -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -20%), derive(-fx-text-box-border, -30%)), + linear-gradient(to bottom, derive(#0b99c9, 30%), #0b99c9); + -fx-background-insets: 0, 1; + +} + +.toggle-switch .thumb-area +{ + -fx-padding: 0.75em 1.333333em 0.75em 1.333333em; /* 7 16 7 16 */ +} + +.toggle-switch:disabled +{ + -fx-opacity: 0.4; +} diff --git a/src/org/controlsfx/control/unselected-star.png b/src/org/controlsfx/control/unselected-star.png new file mode 100644 index 0000000000000000000000000000000000000000..774c240118b6220c788fe44dd97c8af2fdd4430f Binary files /dev/null and b/src/org/controlsfx/control/unselected-star.png differ diff --git a/src/org/controlsfx/dialog/CommandLinksDialog.java b/src/org/controlsfx/dialog/CommandLinksDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..4d2e0e50007190b7a7b13f2f16c257880ef706df --- /dev/null +++ b/src/org/controlsfx/dialog/CommandLinksDialog.java @@ -0,0 +1,298 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import static impl.org.controlsfx.i18n.Localization.getString; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javafx.beans.binding.DoubleBinding; +import javafx.collections.ListChangeListener; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.geometry.VPos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; + +public class CommandLinksDialog extends Dialog<ButtonType> { + + public static class CommandLinksButtonType { + private final ButtonType buttonType; + private final String longText; + private final Node graphic; + private boolean isHidden = false; + + public CommandLinksButtonType(String text, boolean isDefault ) { + this(new ButtonType(text, buildButtonData(isDefault)), null); + } + + public CommandLinksButtonType(String text, String longText, boolean isDefault) { + this(new ButtonType(text, buildButtonData(isDefault)), longText, null); + } + + public CommandLinksButtonType(String text, String longText, Node graphic, boolean isDefault) { + this(new ButtonType(text, buildButtonData(isDefault)), longText, graphic); + } + + private CommandLinksButtonType(ButtonType buttonType) { + this(buttonType, null); + } + + private CommandLinksButtonType(ButtonType buttonType, String longText) { + this(buttonType, longText, null); + } + + private CommandLinksButtonType(ButtonType buttonType, String longText, Node graphic) { + this.buttonType = buttonType; + this.longText = longText; + this.graphic = graphic; + + } + + private static ButtonData buildButtonData( boolean isDeafault) { + return isDeafault? ButtonData.OK_DONE :ButtonData.OTHER; + } + + private static CommandLinksButtonType buildHiddenCancelLink() { + CommandLinksButtonType link = new CommandLinksButtonType(new ButtonType("",ButtonData.CANCEL_CLOSE)); + link.isHidden = true; + return link; + } + + public ButtonType getButtonType() { + return buttonType; + } + + public Node getGraphic() { + return graphic; + } + + public String getLongText() { + return longText; + } + } + + + private final static int gapSize = 10; + + private final Map<ButtonType, CommandLinksButtonType> typeMap; + + private Label contentTextLabel; + + private GridPane grid = new GridPane() { + @Override protected double computePrefWidth(double height) { + boolean isDefault = true; + double pw = 0; + + for (ButtonType buttonType : getDialogPane().getButtonTypes()) { + Button button = (Button) getDialogPane().lookupButton(buttonType); + double buttonPrefWidth = button.getGraphic().prefWidth(-1); + + if (isDefault) { + pw = buttonPrefWidth; + isDefault = false; + } else { + pw = Math.min(pw, buttonPrefWidth); + } + } + return pw + gapSize; + } + + @Override protected double computePrefHeight(double width) { + double ph = getDialogPane().getHeader() == null ? 0 : 10; + + for (ButtonType buttonType : getDialogPane().getButtonTypes()) { + Button button = (Button) getDialogPane().lookupButton(buttonType); + ph += button.prefHeight(width) + gapSize; + } + + // TODO remove magic number + return ph * 1.2; + } + }; + + public CommandLinksDialog(CommandLinksButtonType... links) { + this(Arrays.asList(links)); + } + + public CommandLinksDialog(List<CommandLinksButtonType> links) { + this.grid.setHgap(gapSize); + this.grid.setVgap(gapSize); + this.grid.getStyleClass().add("container"); //$NON-NLS-1$ + + final DialogPane dialogPane = new DialogPane() { + @Override protected Node createButtonBar() { + return null; + } + + @Override protected Node createButton(ButtonType buttonType) { + return createCommandLinksButton(buttonType); + } + }; + setDialogPane(dialogPane); + + setTitle(getString("Dialog.info.title")); //$NON-NLS-1$ + dialogPane.getStyleClass().add("command-links-dialog"); //$NON-NLS-1$ + dialogPane.getStylesheets().add(getClass().getResource("dialogs.css").toExternalForm()); //$NON-NLS-1$ + dialogPane.getStylesheets().add(getClass().getResource("commandlink.css").toExternalForm()); //$NON-NLS-1$ + + // create a map from ButtonType -> CommandLinkButtonType, and put the + // ButtonType values into the dialog pane + + typeMap = new HashMap<>(); + for (CommandLinksButtonType link : links) { + addLinkToDialog(dialogPane,link); + } + addLinkToDialog(dialogPane,CommandLinksButtonType.buildHiddenCancelLink()); + + updateGrid(); + dialogPane.getButtonTypes().addListener((ListChangeListener<? super ButtonType>)c -> updateGrid()); + + contentTextProperty().addListener(o -> updateContentText()); + } + + private void addLinkToDialog(DialogPane dialogPane, CommandLinksButtonType link) { + typeMap.put(link.getButtonType(), link); + dialogPane.getButtonTypes().add(link.getButtonType()); + } + + private void updateContentText() { + String contentText = getDialogPane().getContentText(); + grid.getChildren().remove(contentTextLabel); + if (contentText != null && ! contentText.isEmpty()) { + if (contentTextLabel != null) { + contentTextLabel.setText(contentText); + } else { + contentTextLabel = new Label(getDialogPane().getContentText()); + contentTextLabel.getStyleClass().add("command-link-message"); //$NON-NLS-1$ + } + grid.add(contentTextLabel, 0, 0); + } + } + + private void updateGrid() { + grid.getChildren().clear(); + + // add the message to the top of the dialog + updateContentText(); + + // then build all the buttons + int row = 1; + for (final ButtonType buttonType : getDialogPane().getButtonTypes()) { + if (buttonType == null) continue; + + final Button button = (Button)getDialogPane().lookupButton(buttonType); + + GridPane.setHgrow(button, Priority.ALWAYS); + GridPane.setVgrow(button, Priority.ALWAYS); + grid.add(button, 0, row++); + } + +// // last button gets some extra padding (hacky) +// GridPane.setMargin(buttons.get(buttons.size() - 1), new Insets(0,0,10,0)); + + getDialogPane().setContent(grid); + getDialogPane().requestLayout(); + } + + private Button createCommandLinksButton(ButtonType buttonType) { + // look up the CommandLinkButtonType for the given ButtonType + CommandLinksButtonType commandLink = typeMap.getOrDefault(buttonType, new CommandLinksButtonType(buttonType)); + + + // put the content inside a button + final Button button = new Button(); + button.getStyleClass().addAll("command-link-button"); //$NON-NLS-1$ + button.setMaxHeight(Double.MAX_VALUE); + button.setMaxWidth(Double.MAX_VALUE); + button.setAlignment(Pos.CENTER_LEFT); + + final ButtonData buttonData = buttonType.getButtonData(); + button.setDefaultButton(buttonData != null && buttonData.isDefaultButton()); + button.setOnAction(ae -> setResult(buttonType)); + + final Label titleLabel = new Label(commandLink.getButtonType().getText() ); + titleLabel.minWidthProperty().bind(new DoubleBinding() { + { + bind(titleLabel.prefWidthProperty()); + } + + @Override protected double computeValue() { + return titleLabel.getPrefWidth() + 400; + } + }); + titleLabel.getStyleClass().addAll("line-1"); //$NON-NLS-1$ + titleLabel.setWrapText(true); + titleLabel.setAlignment(Pos.TOP_LEFT); + GridPane.setVgrow(titleLabel, Priority.NEVER); + + Label messageLabel = new Label(commandLink.getLongText() ); + messageLabel.getStyleClass().addAll("line-2"); //$NON-NLS-1$ + messageLabel.setWrapText(true); + messageLabel.setAlignment(Pos.TOP_LEFT); + messageLabel.setMaxHeight(Double.MAX_VALUE); + GridPane.setVgrow(messageLabel, Priority.SOMETIMES); + + Node commandLinkImage = commandLink.getGraphic(); + Node view = commandLinkImage == null ? + new ImageView(CommandLinksDialog.class.getResource("arrow-green-right.png").toExternalForm()) : //$NON-NLS-1$ + commandLinkImage; + Pane graphicContainer = new Pane(view); + graphicContainer.getStyleClass().add("graphic-container"); //$NON-NLS-1$ + GridPane.setValignment(graphicContainer, VPos.TOP); + GridPane.setMargin(graphicContainer, new Insets(0,10,0,0)); + + GridPane grid = new GridPane(); + grid.minWidthProperty().bind(titleLabel.prefWidthProperty()); + grid.setMaxHeight(Double.MAX_VALUE); + grid.setMaxWidth(Double.MAX_VALUE); + grid.getStyleClass().add("container"); //$NON-NLS-1$ + grid.add(graphicContainer, 0, 0, 1, 2); + grid.add(titleLabel, 1, 0); + grid.add(messageLabel, 1, 1); + + button.setGraphic(grid); + button.minWidthProperty().bind(titleLabel.prefWidthProperty()); + + if (commandLink.isHidden) { + button.setVisible(false); + button.setPrefHeight(1); + } + return button; + } +} diff --git a/src/org/controlsfx/dialog/DialogUtils.java b/src/org/controlsfx/dialog/DialogUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..21fdd4c764b10a7cc46e1cab490279106d251f20 --- /dev/null +++ b/src/org/controlsfx/dialog/DialogUtils.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.ButtonBar.ButtonData; + +// package scope +class DialogUtils { + + static void forcefullyHideDialog(javafx.scene.control.Dialog<?> dialog) { + // for the dialog to be able to hide, we need a cancel button, + // so lets put one in now and then immediately call hide, and then + // remove the button again (if necessary). + DialogPane dialogPane = dialog.getDialogPane(); + if (containsCancelButton(dialog)) { + dialog.hide(); + return; + } + + dialogPane.getButtonTypes().add(ButtonType.CANCEL); + dialog.hide(); + dialogPane.getButtonTypes().remove(ButtonType.CANCEL); + } + + static boolean containsCancelButton(Dialog<?> dialog) { + DialogPane dialogPane = dialog.getDialogPane(); + for (ButtonType type : dialogPane.getButtonTypes()) { + if (type.getButtonData() == ButtonData.CANCEL_CLOSE) { + return true; + } + } + return false; + } +} diff --git a/src/org/controlsfx/dialog/ExceptionDialog.java b/src/org/controlsfx/dialog/ExceptionDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..816b51303bb4365a7737cc3d2cad0cc2283230de --- /dev/null +++ b/src/org/controlsfx/dialog/ExceptionDialog.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import static impl.org.controlsfx.i18n.Localization.getString; +import static impl.org.controlsfx.i18n.Localization.localize; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; + +public class ExceptionDialog extends Dialog<ButtonType> { + + public ExceptionDialog(final Throwable exception) { + final DialogPane dialogPane = getDialogPane(); + + setTitle(getString("exception.dlg.title")); //$NON-NLS-1$ + dialogPane.setHeaderText(getString("exception.dlg.header")); //$NON-NLS-1$ + dialogPane.getStyleClass().add("exception-dialog"); //$NON-NLS-1$ + dialogPane.getStylesheets().add(ProgressDialog.class.getResource("dialogs.css").toExternalForm()); //$NON-NLS-1$ + dialogPane.getButtonTypes().addAll(ButtonType.OK); + + // --- content + String contentText = getContentText(); + dialogPane.setContent(new Label(contentText != null && ! contentText.isEmpty() ? + contentText : exception.getMessage())); + + // --- expandable content + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + exception.printStackTrace(pw); + String exceptionText = sw.toString(); + + Label label = new Label( localize(getString("exception.dlg.label"))); //$NON-NLS-1$ + + TextArea textArea = new TextArea(exceptionText); + textArea.setEditable(false); + textArea.setWrapText(true); + + textArea.setMaxWidth(Double.MAX_VALUE); + textArea.setMaxHeight(Double.MAX_VALUE); + GridPane.setVgrow(textArea, Priority.ALWAYS); + GridPane.setHgrow(textArea, Priority.ALWAYS); + + GridPane root = new GridPane(); + root.setMaxWidth(Double.MAX_VALUE); + root.add(label, 0, 0); + root.add(textArea, 0, 1); + + + dialogPane.setExpandableContent(root); + } +} diff --git a/src/org/controlsfx/dialog/FontSelectorDialog.java b/src/org/controlsfx/dialog/FontSelectorDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..f725ae1d2648b5ee6e0b22941b39b31b7993b33e --- /dev/null +++ b/src/org/controlsfx/dialog/FontSelectorDialog.java @@ -0,0 +1,367 @@ +/** + * Copyright (c) 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import javafx.application.Platform; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.transformation.FilteredList; +import javafx.geometry.Pos; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.RowConstraints; +import javafx.scene.layout.StackPane; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Font; +import javafx.scene.text.FontPosture; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import javafx.util.Callback; + +public class FontSelectorDialog extends Dialog<Font> { + + private FontPanel fontPanel; + + public FontSelectorDialog(Font defaultFont) { + fontPanel = new FontPanel(); + fontPanel.setFont(defaultFont); + + setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? fontPanel.getFont() : null); + + final DialogPane dialogPane = getDialogPane(); + + setTitle(localize(asKey("font.dlg.title"))); //$NON-NLS-1$ + dialogPane.setHeaderText(localize(asKey("font.dlg.header"))); //$NON-NLS-1$ + dialogPane.getStyleClass().add("font-selector-dialog"); //$NON-NLS-1$ + dialogPane.getStylesheets().add(FontSelectorDialog.class.getResource("dialogs.css").toExternalForm()); //$NON-NLS-1$ + dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + dialogPane.setContent(fontPanel); + } + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + /** + * Font style as combination of font weight and font posture. + * Weight does not have to be there (represented by null) + * Posture is required, null posture is converted to REGULAR + */ + private static class FontStyle implements Comparable<FontStyle> { + + private FontPosture posture; + private FontWeight weight; + + public FontStyle( FontWeight weight, FontPosture posture ) { + this.posture = posture == null? FontPosture.REGULAR: posture; + this.weight = weight; + } + + public FontStyle() { + this( null, null); + } + + public FontStyle(String styles) { + this(); + String[] fontStyles = (styles == null? "": styles.trim().toUpperCase()).split(" "); //$NON-NLS-1$ //$NON-NLS-2$ + for ( String style: fontStyles) { + FontWeight w = FontWeight.findByName(style); + if ( w != null ) { + weight = w; + } else { + FontPosture p = FontPosture.findByName(style); + if ( p != null ) posture = p; + } + } + } + + public FontStyle(Font font) { + this( font.getStyle()); + } + + public FontPosture getPosture() { + return posture; + } + + public FontWeight getWeight() { + return weight; + } + + + @Override public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((posture == null) ? 0 : posture.hashCode()); + result = prime * result + ((weight == null) ? 0 : weight.hashCode()); + return result; + } + + @Override public boolean equals(Object that) { + if (this == that) + return true; + if (that == null) + return false; + if (getClass() != that.getClass()) + return false; + FontStyle other = (FontStyle) that; + if (posture != other.posture) + return false; + if (weight != other.weight) + return false; + return true; + } + + private static String makePretty(Object o) { + String s = o == null? "": o.toString(); //$NON-NLS-1$ + if ( !s.isEmpty()) { + s = s.replace("_", " "); //$NON-NLS-1$ //$NON-NLS-2$ + s = s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + } + return s; + } + + @Override public String toString() { + return String.format("%s %s", makePretty(weight), makePretty(posture) ).trim(); //$NON-NLS-1$ + } + + private <T extends Enum<T>> int compareEnums( T e1, T e2) { + if ( e1 == e2 ) return 0; + if ( e1 == null ) return -1; + if ( e2 == null ) return 1; + return e1.compareTo(e2); + } + + @Override public int compareTo(FontStyle fs) { + int result = compareEnums(weight,fs.weight); + return ( result != 0 )? result: compareEnums(posture,fs.posture); + } + + } + + + private static class FontPanel extends GridPane { + private static final double HGAP = 10; + private static final double VGAP = 5; + + private static final Predicate<Object> MATCH_ALL = new Predicate<Object>() { + @Override public boolean test(Object t) { + return true; + } + }; + + private static final Double[] fontSizes = new Double[] {8d,9d,11d,12d,14d,16d,18d,20d,22d,24d,26d,28d,36d,48d,72d}; + + private static List<FontStyle> getFontStyles( String fontFamily ) { + Set<FontStyle> set = new HashSet<>(); + for (String f : Font.getFontNames(fontFamily)) { + set.add(new FontStyle(f.replace(fontFamily, ""))); //$NON-NLS-1$ + } + + List<FontStyle> result = new ArrayList<>(set); + Collections.sort(result); + return result; + + } + + + private final FilteredList<String> filteredFontList = new FilteredList<>(FXCollections.observableArrayList(Font.getFamilies()), MATCH_ALL); + private final FilteredList<FontStyle> filteredStyleList = new FilteredList<>(FXCollections.<FontStyle>observableArrayList(), MATCH_ALL); + private final FilteredList<Double> filteredSizeList = new FilteredList<>(FXCollections.observableArrayList(fontSizes), MATCH_ALL); + + private final ListView<String> fontListView = new ListView<>(filteredFontList); + private final ListView<FontStyle> styleListView = new ListView<>(filteredStyleList); + private final ListView<Double> sizeListView = new ListView<>(filteredSizeList); + private final Text sample = new Text(localize(asKey("font.dlg.sample.text"))); //$NON-NLS-1$ + + public FontPanel() { + setHgap(HGAP); + setVgap(VGAP); + setPrefSize(500, 300); + setMinSize(500, 300); + + ColumnConstraints c0 = new ColumnConstraints(); + c0.setPercentWidth(60); + ColumnConstraints c1 = new ColumnConstraints(); + c1.setPercentWidth(25); + ColumnConstraints c2 = new ColumnConstraints(); + c2.setPercentWidth(15); + getColumnConstraints().addAll(c0, c1, c2); + + RowConstraints r0 = new RowConstraints(); + r0.setVgrow(Priority.NEVER); + RowConstraints r1 = new RowConstraints(); + r1.setVgrow(Priority.NEVER); + RowConstraints r2 = new RowConstraints(); + r2.setFillHeight(true); + r2.setVgrow(Priority.NEVER); + RowConstraints r3 = new RowConstraints(); + r3.setPrefHeight(250); + r3.setVgrow(Priority.NEVER); + getRowConstraints().addAll(r0, r1, r2, r3); + + // layout hello.dialog + add(new Label(localize(asKey("font.dlg.font.label"))), 0, 0); //$NON-NLS-1$ + // fontSearch.setMinHeight(Control.USE_PREF_SIZE); + // add( fontSearch, 0, 1); + add(fontListView, 0, 1); + fontListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { + @Override public ListCell<String> call(ListView<String> listview) { + return new ListCell<String>() { + @Override protected void updateItem(String family, boolean empty) { + super.updateItem(family, empty); + + if (! empty) { + setFont(Font.font(family)); + setText(family); + } else { + setText(null); + } + } + }; + } + }); + + + ChangeListener<Object> sampleRefreshListener = new ChangeListener<Object>() { + @Override public void changed(ObservableValue<? extends Object> arg0, Object arg1, Object arg2) { + refreshSample(); + } + }; + + fontListView.selectionModelProperty().get().selectedItemProperty().addListener( new ChangeListener<String>() { + + @Override public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) { + String fontFamily = listSelection(fontListView); + styleListView.setItems(FXCollections.<FontStyle>observableArrayList(getFontStyles(fontFamily))); + refreshSample(); + }}); + + add( new Label(localize(asKey("font.dlg.style.label"))), 1, 0); //$NON-NLS-1$ + // postureSearch.setMinHeight(Control.USE_PREF_SIZE); + // add( postureSearch, 1, 1); + add(styleListView, 1, 1); + styleListView.selectionModelProperty().get().selectedItemProperty().addListener(sampleRefreshListener); + + add( new Label(localize(asKey("font.dlg.size.label"))), 2, 0); //$NON-NLS-1$ + // sizeSearch.setMinHeight(Control.USE_PREF_SIZE); + // add( sizeSearch, 2, 1); + add(sizeListView, 2, 1); + sizeListView.selectionModelProperty().get().selectedItemProperty().addListener(sampleRefreshListener); + + final double height = 45; + final DoubleBinding sampleWidth = new DoubleBinding() { + { + bind(fontListView.widthProperty(), styleListView.widthProperty(), sizeListView.widthProperty()); + } + + @Override protected double computeValue() { + return fontListView.getWidth() + styleListView.getWidth() + sizeListView.getWidth() + 3 * HGAP; + } + }; + StackPane sampleStack = new StackPane(sample); + sampleStack.setAlignment(Pos.CENTER_LEFT); + sampleStack.setMinHeight(height); + sampleStack.setPrefHeight(height); + sampleStack.setMaxHeight(height); + sampleStack.prefWidthProperty().bind(sampleWidth); + Rectangle clip = new Rectangle(0, height); + clip.widthProperty().bind(sampleWidth); + sampleStack.setClip(clip); + add(sampleStack, 0, 3, 1, 3); + } + + public void setFont(final Font font) { + final Font _font = font == null ? Font.getDefault() : font; + if (_font != null) { + selectInList( fontListView, _font.getFamily() ); + selectInList( styleListView, new FontStyle(_font)); + selectInList( sizeListView, _font.getSize() ); + } + } + + public Font getFont() { + try { + FontStyle style = listSelection(styleListView); + if ( style == null ) { + return Font.font( + listSelection(fontListView), + listSelection(sizeListView)); + + } else { + return Font.font( + listSelection(fontListView), + style.getWeight(), + style.getPosture(), + listSelection(sizeListView)); + } + + } catch( Throwable ex ) { + return null; + } + } + + private void refreshSample() { + sample.setFont(getFont()); + } + + private <T> void selectInList( final ListView<T> listView, final T selection ) { + Platform.runLater(new Runnable() { + @Override public void run() { + listView.scrollTo(selection); + listView.getSelectionModel().select(selection); + } + }); + } + + private <T> T listSelection(final ListView<T> listView) { + return listView.selectionModelProperty().get().getSelectedItem(); + } + } +} diff --git a/src/org/controlsfx/dialog/LoginDialog.java b/src/org/controlsfx/dialog/LoginDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..018f7480008aa3e461fc1e0f4f93a7fd50da8547 --- /dev/null +++ b/src/org/controlsfx/dialog/LoginDialog.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import static impl.org.controlsfx.i18n.Localization.getString; +import javafx.application.Platform; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import javafx.util.Callback; +import javafx.util.Pair; + +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.control.textfield.CustomTextField; +import org.controlsfx.control.textfield.TextFields; +import org.controlsfx.validation.ValidationSupport; +import org.controlsfx.validation.Validator; + +public class LoginDialog extends Dialog<Pair<String,String>> { + + private final ButtonType loginButtonType; + private final CustomTextField txUserName; + private final CustomPasswordField txPassword; + + @SuppressWarnings("deprecation") + public LoginDialog(final Pair<String,String> initialUserInfo, final Callback<Pair<String,String>, Void> authenticator) { + final DialogPane dialogPane = getDialogPane(); + + setTitle(getString("login.dlg.title")); //$NON-NLS-1$ + dialogPane.setHeaderText(getString("login.dlg.header")); //$NON-NLS-1$ + dialogPane.getStyleClass().add("login-dialog"); //$NON-NLS-1$ + dialogPane.getStylesheets().add(LoginDialog.class.getResource("dialogs.css").toExternalForm()); //$NON-NLS-1$ + dialogPane.getButtonTypes().addAll(ButtonType.CANCEL); + + + + + + txUserName = (CustomTextField) TextFields.createClearableTextField(); + + txUserName.setLeft(new ImageView(LoginDialog.class.getResource("/org/controlsfx/dialog/user.png").toExternalForm())); //$NON-NLS-1$ + + txPassword = (CustomPasswordField) TextFields.createClearablePasswordField(); + txPassword.setLeft(new ImageView(LoginDialog.class.getResource("/org/controlsfx/dialog/lock.png").toExternalForm())); //$NON-NLS-1$ + + Label lbMessage= new Label(""); //$NON-NLS-1$ + lbMessage.getStyleClass().addAll("message-banner"); //$NON-NLS-1$ + lbMessage.setVisible(false); + lbMessage.setManaged(false); + + final VBox content = new VBox(10); + content.getChildren().add(lbMessage); + content.getChildren().add(txUserName); + content.getChildren().add(txPassword); + + dialogPane.setContent(content); + + loginButtonType = new javafx.scene.control.ButtonType(getString("login.dlg.login.button"), ButtonData.OK_DONE); //$NON-NLS-1$ + dialogPane.getButtonTypes().addAll(loginButtonType); + Button loginButton = (Button) dialogPane.lookupButton(loginButtonType); + loginButton.setOnAction(actionEvent -> { + try { + if (authenticator != null ) { + authenticator.call(new Pair<>(txUserName.getText(), txPassword.getText())); + } + lbMessage.setVisible(false); + lbMessage.setManaged(false); + hide(); +// dlg.setResult(this); + } catch( Throwable ex ) { + lbMessage.setVisible(true); + lbMessage.setManaged(true); + lbMessage.setText(ex.getMessage()); +// sizeToScene(); +// dlg.shake(); + ex.printStackTrace(); + } + }); + +// final Dialog dlg = buildDialog(Type.LOGIN); +// dlg.setContent(content); + +// dlg.setResizable(false); +// dlg.setIconifiable(false); +// if ( dlg.getGraphic() == null ) { +// dlg.setGraphic( new ImageView( DialogResources.getImage("login.icon"))); +// } +// dlg.getActions().setAll(actionLogin, ACTION_CANCEL); + String userNameCation = getString("login.dlg.user.caption"); //$NON-NLS-1$ + String passwordCaption = getString("login.dlg.pswd.caption"); //$NON-NLS-1$ + txUserName.setPromptText(userNameCation); + txUserName.setText(initialUserInfo == null ? "" : initialUserInfo.getKey()); //$NON-NLS-1$ + txPassword.setPromptText(passwordCaption); + txPassword.setText(new String(initialUserInfo == null ? "" : initialUserInfo.getValue())); //$NON-NLS-1$ + + ValidationSupport validationSupport = new ValidationSupport(); + Platform.runLater( () -> { + String requiredFormat = "'%s' is required"; //$NON-NLS-1$ + validationSupport.registerValidator(txUserName, Validator.createEmptyValidator( String.format( requiredFormat, userNameCation ))); + validationSupport.registerValidator(txPassword, Validator.createEmptyValidator(String.format( requiredFormat, passwordCaption ))); +// loginButton.disabledProperty().bind(validationSupport.invalidProperty()); + txUserName.requestFocus(); + } ); + + + setResultConverter(dialogButton -> dialogButton == loginButtonType ? + new Pair<>(txUserName.getText(), txPassword.getText()) : null); + +// return Optional.ofNullable( +// dlg.show() == actionLogin? +// new Pair<String,String>(txUserName.getText(), txPassword.getText()): +// null); + } + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + +} diff --git a/src/org/controlsfx/dialog/ProgressDialog.java b/src/org/controlsfx/dialog/ProgressDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..f57707e0dc9491242ae20d525e08878836a3e094 --- /dev/null +++ b/src/org/controlsfx/dialog/ProgressDialog.java @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import static impl.org.controlsfx.i18n.Localization.getString; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.concurrent.Worker; +import javafx.concurrent.Worker.State; +import javafx.geometry.Insets; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +public class ProgressDialog extends Dialog<Void> { + + + public ProgressDialog(final Worker<?> worker) { + if (worker != null + && (worker.getState() == State.CANCELLED + || worker.getState() == State.FAILED + || worker.getState() == State.SUCCEEDED)) { + return; + } + setResultConverter(dialogButton -> null); + + final DialogPane dialogPane = getDialogPane(); + + setTitle(getString("progress.dlg.title")); //$NON-NLS-1$ + dialogPane.setHeaderText(getString("progress.dlg.header")); //$NON-NLS-1$ + dialogPane.getStyleClass().add("progress-dialog"); //$NON-NLS-1$ + dialogPane.getStylesheets().add(ProgressDialog.class.getResource("dialogs.css").toExternalForm()); //$NON-NLS-1$ + + final Label progressMessage = new Label(); + progressMessage.textProperty().bind(worker.messageProperty()); + + final WorkerProgressPane content = new WorkerProgressPane(this); + content.setMaxWidth(Double.MAX_VALUE); + content.setWorker(worker); + + VBox vbox = new VBox(10, progressMessage, content); + vbox.setMaxWidth(Double.MAX_VALUE); + vbox.setPrefSize(300, 100); + /** + * The content Text cannot be set before the constructor and since we + * set a Content Node, the contentText will not be shown. If we want to + * let the user display a content text, we must recreate it. + */ + Label contentText = new Label(); + contentText.setWrapText(true); + vbox.getChildren().add(0, contentText); + contentText.textProperty().bind(dialogPane.contentTextProperty()); + dialogPane.setContent(vbox); + } + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + /** + * The WorkerProgressPane takes a {@link Dialog} and a {@link Worker} + * and links them together so the dialog is shown or hidden depending + * on the state of the worker. The WorkerProgressPane also includes + * a progress bar that is automatically bound to the progress property + * of the worker. The way in which the WorkerProgressPane shows and + * hides its worker's dialog is consistent with the expected behavior + * for {@link #showWorkerProgress(Worker)}. + */ + private static class WorkerProgressPane extends Region { + private Worker<?> worker; + + private boolean dialogVisible = false; + private boolean cancelDialogShow = false; + + private ChangeListener<Worker.State> stateListener = new ChangeListener<Worker.State>() { + @Override public void changed(ObservableValue<? extends State> observable, State old, State value) { + switch(value) { + case CANCELLED: + case FAILED: + case SUCCEEDED: + if(!dialogVisible) { + cancelDialogShow = true; + end(); + } else if(old == State.SCHEDULED || old == State.RUNNING) { + end(); + } + break; + case SCHEDULED: + begin(); + break; + default: //no-op + } + } + }; + + public final void setWorker(final Worker<?> newWorker) { + if (newWorker != worker) { + if (worker != null) { + worker.stateProperty().removeListener(stateListener); + end(); + } + + worker = newWorker; + + if (newWorker != null) { + newWorker.stateProperty().addListener(stateListener); + if (newWorker.getState() == Worker.State.RUNNING || newWorker.getState() == Worker.State.SCHEDULED) { + // It is already running + begin(); + } + } + } + } + + // If the progress indicator changes, then we need to re-initialize + // If the worker changes, we need to re-initialize + + private final ProgressDialog dialog; + private final ProgressBar progressBar; + + public WorkerProgressPane(ProgressDialog dialog) { + this.dialog = dialog; + + this.progressBar = new ProgressBar(); + progressBar.setMaxWidth(Double.MAX_VALUE); + getChildren().add(progressBar); + + if (worker != null) { + progressBar.progressProperty().bind(worker.progressProperty()); + } + } + + private void begin() { + // Platform.runLater needs to be used to show the dialog because + // the call begin() is going to be occurring when the worker is + // notifying state listeners about changes. If Platform.runLater + // is not used, the call to show() will cause the worker to get + // blocked during notification and it will prevent the worker + // from performing any additional notification for state changes. + // + // Sine the dialog is hidden as a result of a change in worker + // state, calling show() without wrapping it in Platform.runLater + // will cause the progress dialog to run forever when the dialog + // is attached to workers that start out with a state of READY. + // + // This also creates a case where the worker's state can change + // to finished before the dialog is shown, resulting in an + // an attempt to hide the dialog before it is shown. It's + // necessary to track whether or not this occurs, so flags are + // set to indicate if the dialog is visible and if if the call + // to show should still be allowed. + cancelDialogShow = false; + + Platform.runLater(() -> { + if(!cancelDialogShow) { + progressBar.progressProperty().bind(worker.progressProperty()); + dialogVisible = true; + dialog.show(); + } + }); + } + + private void end() { + progressBar.progressProperty().unbind(); + dialogVisible = false; + DialogUtils.forcefullyHideDialog(dialog); + } + + @Override protected void layoutChildren() { + if (progressBar != null) { + Insets insets = getInsets(); + double w = getWidth() - insets.getLeft() - insets.getRight(); + double h = getHeight() - insets.getTop() - insets.getBottom(); + + double prefH = progressBar.prefHeight(-1); + double x = insets.getLeft() + (w - w) / 2.0; + double y = insets.getTop() + (h - prefH) / 2.0; + + progressBar.resizeRelocate(x, y, w, prefH); + } + } + } +} diff --git a/src/org/controlsfx/dialog/Wizard.java b/src/org/controlsfx/dialog/Wizard.java new file mode 100644 index 0000000000000000000000000000000000000000..ee6c0800eef48649855e983ac4f989207bd19747 --- /dev/null +++ b/src/org/controlsfx/dialog/Wizard.java @@ -0,0 +1,737 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import static impl.org.controlsfx.i18n.Localization.asKey; +import static impl.org.controlsfx.i18n.Localization.localize; +import impl.org.controlsfx.ImplUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.Stack; +import java.util.function.BooleanSupplier; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Rectangle2D; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.layout.Pane; +import javafx.stage.Screen; +import javafx.stage.Window; + +import org.controlsfx.tools.ValueExtractor; +import org.controlsfx.validation.ValidationSupport; + +/** + * <p>The API for creating multi-page Wizards, based on JavaFX {@link Dialog} API.<br> + * Wizard can be setup in following few steps:</p> + * <ul> + * <li>Design wizard pages by inheriting them from {@link WizardPane}</li> + * <li>Define wizard flow by implementing {@link Wizard.Flow}</li> + * <li>Create and instance of the Wizard and assign flow to it</li> + * <li>Execute the wizard using showAndWait method</li> + * <li>Values can be extracted from settings map by calling getSettings + * </ul> + * <p>For simple, linear wizards, the {@link LinearFlow} can be used. + * It is a flow based on a collection of wizard pages. Here is the example:</p> + * + * <pre>{@code // Create pages. Here for simplicity we just create and instance of WizardPane. + * WizardPane page1 = new WizardPane(); + * WizardPane page2 = new WizardPane(); + * WizardPane page2 = new WizardPane(); + * + * // create wizard + * Wizard wizard = new Wizard(); + * + * // create and assign the flow + * wizard.setFlow(new LinearFlow(page1, page2, page3)); + * + * // show wizard and wait for response + * wizard.showAndWait().ifPresent(result -> { + * if (result == ButtonType.FINISH) { + * System.out.println("Wizard finished, settings: " + wizard.getSettings()); + * } + * });}</pre> + * + * <p>For more complex wizard flows we suggest to create a custom ones, describing page traversal logic. + * Here is a simplified example: </p> + * + * <pre>{@code Wizard.Flow branchingFlow = new Wizard.Flow() { + * public Optional<WizardPane> advance(WizardPane currentPage) { + * return Optional.of(getNext(currentPage)); + * } + * + * public boolean canAdvance(WizardPane currentPage) { + * return currentPage != page3; + * } + * + * private WizardPane getNext(WizardPane currentPage) { + * if ( currentPage == null ) { + * return page1; + * } else if ( currentPage == page1) { + * // skipNextPage() does not exist - this just represents that you + * // can add a conditional statement here to change the page. + * return page1.skipNextPage()? page3: page2; + * } else { + * return page3; + * } + * } + * };}</pre> + */ +public class Wizard { + + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private Dialog<ButtonType> dialog; + + private final ObservableMap<String, Object> settings = FXCollections.observableHashMap(); + + private final Stack<WizardPane> pageHistory = new Stack<>(); + + private Optional<WizardPane> currentPage = Optional.empty(); + + private final BooleanProperty invalidProperty = new SimpleBooleanProperty(false); + + // Read settings activated by default for backward compatibility + private final BooleanProperty readSettingsProperty = new SimpleBooleanProperty(true); + + private final ButtonType BUTTON_PREVIOUS = new ButtonType(localize(asKey("wizard.previous.button")), ButtonData.BACK_PREVIOUS); //$NON-NLS-1$ + private final EventHandler<ActionEvent> BUTTON_PREVIOUS_ACTION_HANDLER = actionEvent -> { + actionEvent.consume(); + currentPage = Optional.ofNullable( pageHistory.isEmpty()? null: pageHistory.pop() ); + updatePage(dialog,false); + }; + + private final ButtonType BUTTON_NEXT = new ButtonType(localize(asKey("wizard.next.button")), ButtonData.NEXT_FORWARD); //$NON-NLS-1$ + private final EventHandler<ActionEvent> BUTTON_NEXT_ACTION_HANDLER = actionEvent -> { + actionEvent.consume(); + currentPage.ifPresent(page->pageHistory.push(page)); + currentPage = getFlow().advance(currentPage.orElse(null)); + updatePage(dialog,true); + }; + + private final StringProperty titleProperty = new SimpleStringProperty(); + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates an instance of the wizard without an owner. + */ + public Wizard() { + this(null); + } + + /** + * Creates an instance of the wizard with the given owner. + * @param owner The object from which the owner window is deduced (typically + * this is a Node, but it may also be a Scene or a Stage). + */ + public Wizard(Object owner) { + this(owner, ""); //$NON-NLS-1$ + } + + /** + * Creates an instance of the wizard with the given owner and title. + * + * @param owner The object from which the owner window is deduced (typically + * this is a Node, but it may also be a Scene or a Stage). + * @param title The wizard title. + */ + public Wizard(Object owner, String title) { + + invalidProperty.addListener( (o, ov, nv) -> validateActionState()); + + dialog = new Dialog<>(); + dialog.titleProperty().bind(this.titleProperty); + setTitle(title); + + Window window = null; + if ( owner instanceof Window) { + window = (Window)owner; + } else if ( owner instanceof Node ) { + window = ((Node)owner).getScene().getWindow(); + } + + dialog.initOwner(window); + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + +// /** +// * Shows the wizard but does not wait for a user response (in other words, +// * this brings up a non-blocking dialog). Users of this API must either +// * poll the {@link #resultProperty() result property}, or else add a listener +// * to the result property to be informed of when it is set. +// */ +// public final void show() { +// dialog.show(); +// } + + /** + * Shows the wizard and waits for the user response (in other words, brings + * up a blocking dialog, with the returned value the users input). + * + * @return An {@link Optional} that contains the result. + */ + public final Optional<ButtonType> showAndWait() { + return dialog.showAndWait(); + } + + /** + * @return {@link Dialog#resultProperty()} of the {@link Dialog} representing this {@link Wizard}. + */ + public final ObjectProperty<ButtonType> resultProperty() { + return dialog.resultProperty(); + } + + /** + * The settings map is the place where all data from pages is kept once the + * user moves on from the page, assuming there is a {@link ValueExtractor} + * that is capable of extracting a value out of the various fields on the page. + */ + public final ObservableMap<String, Object> getSettings() { + return settings; + } + + + + /************************************************************************** + * + * Properties + * + **************************************************************************/ + + // --- title + + /** + * Return the titleProperty of the wizard. + */ + public final StringProperty titleProperty() { + return titleProperty; + } + + /** + * Return the title of the wizard. + */ + public final String getTitle() { + return titleProperty.get(); + } + + /** + * Change the Title of the wizard. + * @param title + */ + public final void setTitle( String title ) { + titleProperty.set(title); + } + + // --- flow + /** + * The {@link Flow} property represents the flow of pages in the wizard. + */ + private ObjectProperty<Flow> flow = new SimpleObjectProperty<Flow>(new LinearFlow()) { + @Override protected void invalidated() { + updatePage(dialog,false); + } + + @Override public void set(Flow flow) { + super.set(flow); + pageHistory.clear(); + if ( flow != null ) { + currentPage = flow.advance(currentPage.orElse(null)); + updatePage(dialog,true); + } + }; + }; + + public final ObjectProperty<Flow> flowProperty() { + return flow; + } + + /** + * Returns the currently set {@link Flow}, which represents the flow of + * pages in the wizard. + */ + public final Flow getFlow() { + return flow.get(); + } + + /** + * Sets the {@link Flow}, which represents the flow of pages in the wizard. + */ + public final void setFlow(Flow flow) { + this.flow.set(flow); + } + + + // --- Properties + private static final Object USER_DATA_KEY = new Object(); + + // A map containing a set of properties for this Wizard + private ObservableMap<Object, Object> properties; + + /** + * Returns an observable map of properties on this Wizard for use primarily + * by application developers - not to be confused with the + * {@link #getSettings()} map that represents the values entered by the user + * into the wizard. + * + * @return an observable map of properties on this Wizard for use primarily + * by application developers + */ + public final ObservableMap<Object, Object> getProperties() { + if (properties == null) { + properties = FXCollections.observableMap(new HashMap<>()); + } + return properties; + } + + /** + * Tests if this Wizard has properties. + * @return true if this Wizard has properties. + */ + public boolean hasProperties() { + return properties != null && !properties.isEmpty(); + } + + + // --- UserData + /** + * Convenience method for setting a single Object property that can be + * retrieved at a later date. This is functionally equivalent to calling + * the getProperties().put(Object key, Object value) method. This can later + * be retrieved by calling {@link #getUserData()}. + * + * @param value The value to be stored - this can later be retrieved by calling + * {@link #getUserData()}. + */ + public void setUserData(Object value) { + getProperties().put(USER_DATA_KEY, value); + } + + /** + * Returns a previously set Object property, or null if no such property + * has been set using the {@link #setUserData(Object)} method. + * + * @return The Object that was previously set, or null if no property + * has been set or if null was set. + */ + public Object getUserData() { + return getProperties().get(USER_DATA_KEY); + } + + /** + * Sets the value of the property {@code invalid}. + * + * @param invalid The new validation state + * {@link #invalidProperty() } + */ + public final void setInvalid(boolean invalid) { + invalidProperty.set(invalid); + } + + /** + * Gets the value of the property {@code invalid}. + * + * @return The validation state + * @see #invalidProperty() + */ + public final boolean isInvalid() { + return invalidProperty.get(); + } + + /** + * Property for overriding the individual validation state of this {@link Wizard}. + * Setting {@code invalid} to true will disable the next/finish Button and the user + * will not be able to advance to the next page of the {@link Wizard}. Setting + * {@code invalid} to false will enable the next/finish Button. <br> + * <br> + * For example you can use the {@link ValidationSupport#invalidProperty()} of a + * page and bind it to the {@code invalid} property: <br> + * {@code + * wizard.invalidProperty().bind(page.validationSupport.invalidProperty()); + * } + * + * @return The validation state property + */ + public final BooleanProperty invalidProperty() { + return invalidProperty; + } + + /** + * Sets the value of the property {@code readSettings}. + * + * @param readSettings The new read-settings state + * @see #readSettingsProperty() + */ + public final void setReadSettings(boolean readSettings) { + readSettingsProperty.set(readSettings); + } + + /** + * Gets the value of the property {@code readSettings}. + * + * @return The read-settings state + * @see #readSettingsProperty() + */ + public final boolean isReadSettings() { + return readSettingsProperty.get(); + } + + /** + * Property for overriding the individual read-settings state of this {@link Wizard}. + * Setting {@code readSettings} to true will enable the value extraction for this + * {@link Wizard}. Setting {@code readSettings} to false will disable the value + * extraction for this {@link Wizard}. + * + * @return The readSettings state property + */ + public final BooleanProperty readSettingsProperty() { + return readSettingsProperty; + } + + + + /************************************************************************** + * + * Private implementation + * + **************************************************************************/ + + private void updatePage(Dialog<ButtonType> dialog, boolean advancing) { + Flow flow = getFlow(); + if (flow == null) { + return; + } + + Optional<WizardPane> prevPage = Optional.ofNullable( pageHistory.isEmpty()? null: pageHistory.peek()); + prevPage.ifPresent( page -> { + // if we are going forward in the wizard, we read in the settings + // from the page and store them in the settings map. + // If we are going backwards, we do nothing + // This is only performed if readSettings is true. + if (advancing && isReadSettings()) { + readSettings(page); + } + + // give the previous wizard page a chance to update the pages list + // based on the settings it has received + page.onExitingPage(this); + }); + + currentPage.ifPresent(currentPage -> { + // put in default actions + List<ButtonType> buttons = currentPage.getButtonTypes(); + if (! buttons.contains(BUTTON_PREVIOUS)) { + buttons.add(BUTTON_PREVIOUS); + Button button = (Button)currentPage.lookupButton(BUTTON_PREVIOUS); + button.addEventFilter(ActionEvent.ACTION, BUTTON_PREVIOUS_ACTION_HANDLER); + } + if (! buttons.contains(BUTTON_NEXT)) { + buttons.add(BUTTON_NEXT); + Button button = (Button)currentPage.lookupButton(BUTTON_NEXT); + button.addEventFilter(ActionEvent.ACTION, BUTTON_NEXT_ACTION_HANDLER); + } + if (! buttons.contains(ButtonType.FINISH)) buttons.add(ButtonType.FINISH); + if (! buttons.contains(ButtonType.CANCEL)) buttons.add(ButtonType.CANCEL); + + // then give user a chance to modify the default actions + currentPage.onEnteringPage(this); + + // Remove from DecorationPane which has been created by e.g. validation + if (currentPage.getParent() != null && currentPage.getParent() instanceof Pane) { + Pane parentOfCurrentPage = (Pane) currentPage.getParent(); + parentOfCurrentPage.getChildren().remove(currentPage); + } + + // Get current position and size + double previousX = dialog.getX(); + double previousY = dialog.getY(); + double previousWidth = dialog.getWidth(); + double previousHeight = dialog.getHeight(); + // and then switch to the new pane + dialog.setDialogPane(currentPage); + // Resize Wizard to new page + Window wizard = currentPage.getScene().getWindow(); + wizard.sizeToScene(); + // Center resized Wizard to previous position + + + if (!Double.isNaN(previousX) && !Double.isNaN(previousY)) { + double newWidth = dialog.getWidth(); + double newHeight = dialog.getHeight(); + int newX = (int) (previousX + (previousWidth / 2.0) - (newWidth / 2.0)); + int newY = (int) (previousY + (previousHeight / 2.0) - (newHeight / 2.0)); + + ObservableList<Screen> screens = Screen.getScreensForRectangle(previousX, previousY, 1, 1); + Screen screen = screens.isEmpty() ? Screen.getPrimary() : screens.get(0); + Rectangle2D scrBounds = screen.getBounds(); + int minX = (int)Math.round(scrBounds.getMinX()); + int maxX = (int)Math.round(scrBounds.getMaxX()); + int minY = (int)Math.round(scrBounds.getMinY()); + int maxY = (int)Math.round(scrBounds.getMaxY()); + if(newX + newWidth > maxX) { + newX = maxX - (int)Math.round(newWidth); + } + if(newY + newHeight > maxY) { + newY = maxY - (int)Math.round(newHeight); + } + if(newX < minX) { + newX = minX; + } + if(newY < minY) { + newY = minY; + } + + dialog.setX(newX); + dialog.setY(newY); + } + }); + + validateActionState(); + } + + private void validateActionState() { + final List<ButtonType> currentPaneButtons = dialog.getDialogPane().getButtonTypes(); + + if (getFlow().canAdvance(currentPage.orElse(null))) { + currentPaneButtons.remove(ButtonType.FINISH); + } else { + currentPaneButtons.remove(BUTTON_NEXT); + } + + validateButton( BUTTON_PREVIOUS, () -> pageHistory.isEmpty()); + validateButton( BUTTON_NEXT, () -> invalidProperty.get()); + validateButton( ButtonType.FINISH, () -> invalidProperty.get()); + + } + + // Functional design allows to delay condition evaluation until it is actually needed + private void validateButton( ButtonType buttonType, BooleanSupplier condition) { + Button btn = (Button)dialog.getDialogPane().lookupButton(buttonType); + if ( btn != null ) { + Node focusOwner = (btn.getScene() != null) ? btn.getScene().getFocusOwner() : null; + btn.setDisable(condition.getAsBoolean()); + if(focusOwner != null) { + focusOwner.requestFocus(); + } + } + } + + private int settingCounter; + private void readSettings(WizardPane page) { + // for now we cannot know the structure of the page, so we just drill down + // through the entire scenegraph (from page.content down) until we get + // to the leaf nodes. We stop only if we find a node that is a + // ValueContainer (either by implementing the interface), or being + // listed in the internal valueContainers map. + + settingCounter = 0; + checkNode(page.getContent()); + } + + private boolean checkNode(Node n) { + boolean success = readSetting(n); + + if (success) { + // we've added the setting to the settings map and we should stop drilling deeper + return true; + } else { + /** + * go into children of this node (if possible) and see if we can get + * a value from them (recursively) We use reflection to fix + * https://bitbucket.org/controlsfx/controlsfx/issue/412 . + */ + List<Node> children = ImplUtils.getChildren(n, true); + + // we're doing a depth-first search, where we stop drilling down + // once we hit a successful read + boolean childSuccess = false; + for (Node child : children) { + childSuccess |= checkNode(child); + } + return childSuccess; + } + } + + private boolean readSetting(Node n) { + if (n == null) { + return false; + } + + Object setting = ValueExtractor.getValue(n); + + if (setting != null) { + // save it into the settings map. + // if the node has an id set, we will use that as the setting name + String settingName = n.getId(); + + // but if the id is not set, we will use a generic naming scheme + if (settingName == null || settingName.isEmpty()) { + settingName = "page_" /*+ previousPageIndex*/ + ".setting_" + settingCounter; //$NON-NLS-1$ //$NON-NLS-2$ + } + + getSettings().put(settingName, setting); + + settingCounter++; + } + + return setting != null; + } + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + + /** + * Represents the page flow of the wizard. It defines only methods required + * to move forward in the wizard logic, as backward movement is automatically + * handled by wizard itself, using internal page history. + */ + public interface Flow { + + /** + * Advances the wizard to the next page if possible. + * + * @param currentPage The current wizard page + * @return {@link Optional} value containing the next wizard page. + */ + Optional<WizardPane> advance(WizardPane currentPage); + + /** + * Check if advancing to the next page is possible + * + * @param currentPage The current wizard page + * @return true if it is possible to advance to the next page, false otherwise. + */ + boolean canAdvance(WizardPane currentPage); + } + + + /** + * LinearFlow is an implementation of the {@link Wizard.Flow} interface, + * designed to support the most common type of wizard flow - namely, a linear + * wizard page flow (i.e. through all pages in the order that they are specified). + * Therefore, this {@link Flow} implementation simply traverses a collections of + * {@link WizardPane WizardPanes}. + * + * <p>For example of how to use this API, please refer to the {@link Wizard} + * documentation</p> + * + * @see Wizard + * @see WizardPane + */ + public static class LinearFlow implements Wizard.Flow { + + private final List<WizardPane> pages; + + /** + * Creates a new LinearFlow instance that will allow for stepping through + * the given collection of {@link WizardPane} instances. + */ + public LinearFlow( Collection<WizardPane> pages ) { + this.pages = new ArrayList<>(pages); + } + + /** + * Creates a new LinearFlow instance that will allow for stepping through + * the given varargs array of {@link WizardPane} instances. + */ + public LinearFlow( WizardPane... pages ) { + this( Arrays.asList(pages)); + } + + /** {@inheritDoc} */ + @Override public Optional<WizardPane> advance(WizardPane currentPage) { + int pageIndex = pages.indexOf(currentPage); + return Optional.ofNullable( pages.get(++pageIndex) ); + } + + /** {@inheritDoc} */ + @Override public boolean canAdvance(WizardPane currentPage) { + int pageIndex = pages.indexOf(currentPage); + return pages.size()-1 > pageIndex; + } + } + + + + /************************************************************************** + * + * Methods for the sake of unit tests + * + **************************************************************************/ + + /** + * @return The {@link Dialog} representing this {@link Wizard}. <br> + * This is actually for {@link Dialog} reading-purposes, e.g. + * unit testing the {@link DialogPane} content. + */ + Dialog<ButtonType> getDialog() { + return dialog; + } + +} diff --git a/src/org/controlsfx/dialog/WizardPane.java b/src/org/controlsfx/dialog/WizardPane.java new file mode 100644 index 0000000000000000000000000000000000000000..4379b337572c86ee5d63b6c7af0ae2205433c741 --- /dev/null +++ b/src/org/controlsfx/dialog/WizardPane.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2014, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.dialog; + +import javafx.scene.control.DialogPane; + +/** + * WizardPane is the base class for all wizard pages. The API is essentially + * the {@link DialogPane}, with the addition of convenience methods related + * to {@link #onEnteringPage(Wizard) entering} and + * {@link #onExitingPage(Wizard) exiting} the page. + */ +public class WizardPane extends DialogPane { + + /** + * Creates an instance of wizard pane. + */ + public WizardPane() { + getStylesheets().add(Wizard.class.getResource("wizard.css").toExternalForm()); + getStyleClass().add("wizard-pane"); + } + + /** + * Called on entering a page. This is a good place to read values from wizard settings + * and assign them to controls on the page + * @param wizard which page will be used on + */ + public void onEnteringPage(Wizard wizard) { + // no-op + } + + /** + * Called on existing the page. + * This is a good place to read values from page controls and store them in wizard settings + * @param wizard which page was used on + */ + public void onExitingPage(Wizard wizard) { + // no-op + } +} diff --git a/src/org/controlsfx/dialog/arrow-green-right.png b/src/org/controlsfx/dialog/arrow-green-right.png new file mode 100644 index 0000000000000000000000000000000000000000..63b675cd66fc900476e853e2fa5b8ff0462dcfca Binary files /dev/null and b/src/org/controlsfx/dialog/arrow-green-right.png differ diff --git a/src/org/controlsfx/dialog/commandlink.css b/src/org/controlsfx/dialog/commandlink.css new file mode 100644 index 0000000000000000000000000000000000000000..af693b3c16f0dea6affbd1ebd8046da216704e5e --- /dev/null +++ b/src/org/controlsfx/dialog/commandlink.css @@ -0,0 +1,71 @@ +/******************************************************************************* + * * + * Command Link * + * * + ******************************************************************************/ + /* For the text displayed above command-link buttons (but below the header) */ +.command-links-dialog.dialog-pane > .container > .command-link-message { + -fx-font-size: 1.25em; +} + +.command-links-dialog.dialog-pane > .container > .command-link-button { + -fx-padding: 10 10 10 10; + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-border-color: transparent; + -fx-border-width: 1; + -fx-border-radius: 3px; +} + +.command-links-dialog.dialog-pane > .container > .command-link-button:hover { + -fx-border-color: -fx-box-border; + -fx-background-color: linear-gradient(to bottom, + white, + derive(-fx-box-border, 60%) + ); +} + +.command-links-dialog.dialog-pane > .container > .command-link-button:armed { + -fx-background-color: linear-gradient(to bottom, + white, + derive(-fx-box-border, 40%) + ); +} + +.command-links-dialog.dialog-pane > .container > .command-link-button:default { + -fx-border-color: -fx-default-button; + -fx-background-color: linear-gradient(to bottom, + white, + derive(-fx-default-button, 80%) + ); +} + +.command-links-dialog.dialog-pane > .container > .command-link-button:default:hover { + -fx-border-color: -fx-default-button; + -fx-background-color: linear-gradient(to bottom, + white, + derive(-fx-default-button, 60%) + ); +} + +.command-links-dialog.dialog-pane > .container > .command-link-button:default:armed { + -fx-border-color: -fx-default-button; + -fx-background-color: linear-gradient(to bottom, + white, + derive(-fx-default-button, 40%) + ); +} + +.command-links-dialog.dialog-pane > .container > .command-link-button > .container > .line-1 { + -fx-font-size: 1.25em; + -fx-padding: -5 0 0 5; +} + +.command-links-dialog.dialog-pane > .container > .command-link-button > .container > .line-2 { + -fx-font-size: 1em; + -fx-padding: 0 0 0 5; +} + +.command-links-dialog.dialog-pane > .container > .command-link-button > .container > .graphic-container { + -fx-padding: 10 10 0 0 +} \ No newline at end of file diff --git a/src/org/controlsfx/dialog/dialog-confirm.png b/src/org/controlsfx/dialog/dialog-confirm.png new file mode 100644 index 0000000000000000000000000000000000000000..adb569bece45425ad0742a5b894ae9766596efe9 Binary files /dev/null and b/src/org/controlsfx/dialog/dialog-confirm.png differ diff --git a/src/org/controlsfx/dialog/dialog-error.png b/src/org/controlsfx/dialog/dialog-error.png new file mode 100644 index 0000000000000000000000000000000000000000..769d7df7113350020cf67b19aad6629f68732a49 Binary files /dev/null and b/src/org/controlsfx/dialog/dialog-error.png differ diff --git a/src/org/controlsfx/dialog/dialog-information.png b/src/org/controlsfx/dialog/dialog-information.png new file mode 100644 index 0000000000000000000000000000000000000000..a220108dcf27810ecab177dd7969c7d3d05eae99 Binary files /dev/null and b/src/org/controlsfx/dialog/dialog-information.png differ diff --git a/src/org/controlsfx/dialog/dialog-warning.png b/src/org/controlsfx/dialog/dialog-warning.png new file mode 100644 index 0000000000000000000000000000000000000000..a374f4f7075eb7a97d5f69ddeeaca35be7ab8ad8 Binary files /dev/null and b/src/org/controlsfx/dialog/dialog-warning.png differ diff --git a/src/org/controlsfx/dialog/dialogs.css b/src/org/controlsfx/dialog/dialogs.css new file mode 100644 index 0000000000000000000000000000000000000000..2e93ee52f887e9936d177fcf397388d3fa149ea0 --- /dev/null +++ b/src/org/controlsfx/dialog/dialogs.css @@ -0,0 +1,13 @@ +.progress-dialog.dialog-pane { + -fx-graphic: url("dialog-information.png"); +} + +.font-selector-dialog.dialog-pane, +.login-dialog.dialog-pane, +.command-links-dialog.dialog-pane { + -fx-graphic: url("dialog-confirm.png"); +} + +.exception-dialog.dialog-pane { + -fx-graphic: url("dialog-error.png"); +} \ No newline at end of file diff --git a/src/org/controlsfx/dialog/fewer-details.png b/src/org/controlsfx/dialog/fewer-details.png new file mode 100644 index 0000000000000000000000000000000000000000..c45d4ac09eaa74b5ab9894fa5eef2f28e9c46376 Binary files /dev/null and b/src/org/controlsfx/dialog/fewer-details.png differ diff --git a/src/org/controlsfx/dialog/license.txt b/src/org/controlsfx/dialog/license.txt new file mode 100644 index 0000000000000000000000000000000000000000..07c519bcca52249f0ab3f6e272fe32188d1264e1 --- /dev/null +++ b/src/org/controlsfx/dialog/license.txt @@ -0,0 +1,7 @@ +Some of these images are from the Oxygen icon set. +Oxygen icons are licensed under the +Creative Common Attribution-ShareAlike 3.0 License +http://creativecommons.org/licenses/by-sa/3.0/ + +For reference, these dialog icons were taken from +http://websvn.kde.org/trunk/KDE/kdebase/runtime/pics/oxygen/?pathrev=706996 \ No newline at end of file diff --git a/src/org/controlsfx/dialog/lock.png b/src/org/controlsfx/dialog/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7a9f651dd1c5e06ba6823b44e19deb60f1e6d4 Binary files /dev/null and b/src/org/controlsfx/dialog/lock.png differ diff --git a/src/org/controlsfx/dialog/more-details.png b/src/org/controlsfx/dialog/more-details.png new file mode 100644 index 0000000000000000000000000000000000000000..b8896c70d7c216137b48d95a346c37f59a7b9588 Binary files /dev/null and b/src/org/controlsfx/dialog/more-details.png differ diff --git a/src/org/controlsfx/dialog/package-info.java b/src/org/controlsfx/dialog/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..0d1019c0bc5310d92f9501a7ddc5326ec829ccf7 --- /dev/null +++ b/src/org/controlsfx/dialog/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing a powerful (yet easy to use) dialogs API for showing + * modal dialogs in JavaFX-based applications. + */ +package org.controlsfx.dialog; \ No newline at end of file diff --git a/src/org/controlsfx/dialog/user.png b/src/org/controlsfx/dialog/user.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf6711963aceab5b3e5fc2fd255dfc519e3f32c Binary files /dev/null and b/src/org/controlsfx/dialog/user.png differ diff --git a/src/org/controlsfx/dialog/wizard-page.png b/src/org/controlsfx/dialog/wizard-page.png new file mode 100644 index 0000000000000000000000000000000000000000..3c86ba7d4deffabfb3fda82e60f6318eefddace7 Binary files /dev/null and b/src/org/controlsfx/dialog/wizard-page.png differ diff --git a/src/org/controlsfx/dialog/wizard.css b/src/org/controlsfx/dialog/wizard.css new file mode 100644 index 0000000000000000000000000000000000000000..d8e268916e9e261565170519570b119e9bd405b0 --- /dev/null +++ b/src/org/controlsfx/dialog/wizard.css @@ -0,0 +1,3 @@ +.wizard-pane { + -fx-graphic: url("wizard-page.png"); +} \ No newline at end of file diff --git a/src/org/controlsfx/glyphfont/FontAwesome.java b/src/org/controlsfx/glyphfont/FontAwesome.java new file mode 100644 index 0000000000000000000000000000000000000000..1404e26441c43253b721752e5700cc4f57c983e5 --- /dev/null +++ b/src/org/controlsfx/glyphfont/FontAwesome.java @@ -0,0 +1,723 @@ +/** + * Copyright (c) 2013,2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.glyphfont; + +import java.io.InputStream; +import java.util.Arrays; + + +/** + * Defines a {@link GlyphFont} for the FontAwesome font set (see + * <a href="http://fortawesome.github.io/Font-Awesome/">the FontAwesome website</a> + * for more details). Note that at present the FontAwesome font is not distributed + * with ControlsFX, and is, by default, instead loaded from a CDN at runtime. + * + * <p>To use FontAwesome (or indeed any glyph font) in your JavaFX application, + * you firstly have to get access to the FontAwesome glyph font. You do this by + * doing the following: + * + * <pre>GlyphFont fontAwesome = GlyphFontRegistry.font("FontAwesome");</pre> + * + * <p>This code works because all glyph fonts are found dynamically at runtime + * by the {@link GlyphFontRegistry} class, so you can simply request the font + * set you want from there. + * + * <p>Once the font set has been loaded, you can simply start creating + * {@link Glyph} nodes and place them in your user interface. For example: + * + * <pre>new Button("", fontAwesome.create(\uf013).fontColor(Color.RED));</pre> + * + * <p>Of course, this requires you to know that <code>\uf013</code> maps to + * a 'gear' icon, which is not always intuitive (especially when you re-read the + * code in the future). A simpler approach is to do the following: + * + * <pre>new Button("", fontAwesome.create(FontAwesome.Glyph.GEAR));</pre> + * or + * <pre>new Button("", fontAwesome.create("GEAR"));</pre> + * + * It is possible to achieve the same result without creating a reference to icon font by simply using + * {@link org.controlsfx.glyphfont.Glyph} constructor + * + * <pre>new Button("", new Glyph("FontAwesome","GEAR");</pre> + * + * You can use the above Glyph class also in FXML and set the + * fontFamily and icon property there. + * + * @see GlyphFont + * @see GlyphFontRegistry + * @see Glyph + */ +public class FontAwesome extends GlyphFont { + + private static String fontName = "FontAwesome"; //$NON-NLS-1$ + + + /** + * The individual glyphs offered by the FontAwesome font. + */ + public static enum Glyph implements INamedCharacter { + + ADJUST('\uf042'), + ADN('\uf170'), + ALIGN_CENTER('\uf037'), + ALIGN_JUSTIFY('\uf039'), + ALIGN_LEFT('\uf036'), + ALIGN_RIGHT('\uf038'), + AMBULANCE('\uf0F9'), + ANCHOR('\uf13D'), + ANDROID('\uf17B'), + ANGELLIST('\uf209'), + ANGLE_DOUBLE_DOWN('\uf103'), + ANGLE_DOUBLE_LEFT('\uf100'), + ANGLE_DOUBLE_RIGHT('\uf101'), + ANGLE_DOUBLE_UP('\uf102'), + ANGLE_DOWN('\uf107'), + ANGLE_LEFT('\uf104'), + ANGLE_RIGHT('\uf105'), + ANGLE_UP('\uf106'), + APPLE('\uf179'), + ARCHIVE('\uf187'), + AREA_CHART('\uf1FE'), + ARROW_CIRCLE_DOWN('\uf0AB'), + ARROW_CIRCLE_LEFT('\uf0A8'), + ARROW_CIRCLE_O_DOWN('\uf01A'), + ARROW_CIRCLE_O_LEFT('\uf190'), + ARROW_CIRCLE_O_RIGHT('\uf18E'), + ARROW_CIRCLE_O_UP('\uf01B'), + ARROW_CIRCLE_RIGHT('\uf0A9'), + ARROW_CIRCLE_UP('\uf0AA'), + ARROW_DOWN('\uf063'), + ARROW_LEFT('\uf060'), + ARROW_RIGHT('\uf061'), + ARROW_UP('\uf062'), + ARROWS('\uf047'), + ARROWS_ALT('\uf0B2'), + ARROWS_H('\uf07E'), + ARROWS_V('\uf07D'), + ASTERISK('\uf069'), + AT('\uf1FA'), + AUTOMOBILE('\uf1B9'), + BACKWARD('\uf04A'), + BAN('\uf05E'), + BANK('\uf19C'), + BAR_CHART('\uf080'), + BAR_CHART_O('\uf080'), + BARCODE('\uf02A'), + BARS('\uf0C9'), + BED('\uf236'), + BEER('\uf0FC'), + BEHANCE('\uf1B4'), + BEHANCE_SQUARE('\uf1B5'), + BELL('\uf0F3'), + BELL_O('\uf0A2'), + BELL_SLASH('\uf1F6'), + BELL_SLASH_O('\uf1F7'), + BICYCLE('\uf206'), + BINOCULARS('\uf1E5'), + BIRTHDAY_CAKE('\uf1FD'), + BITBUCKET('\uf171'), + BITBUCKET_SQUARE('\uf172'), + BITCOIN('\uf15A'), + BOLD('\uf032'), + BOLT('\uf0E7'), + BOMB('\uf1E2'), + BOOK('\uf02D'), + BOOKMARK('\uf02E'), + BOOKMARK_ALT('\uf097'), + BRIEFCASE('\uf0B1'), + BTC('\uf15A'), + BUG('\uf188'), + BUILDING('\uf1AD'), + BUILDING_ALT('\uf0F7'), + BULLHORN('\uf0A1'), + BULLSEYE('\uf140'), + BUS('\uf207'), + BUYSELLADS('\uf20D'), + CAB('\uf1BA'), + CALCULATOR('\uf1EC'), + CALENDAR('\uf073'), + CALENDAR_ALT('\uf133'), + CAMERA('\uf030'), + CAMERA_RETRO('\uf083'), + CAR('\uf1B9'), + CARET_DOWN('\uf0D7'), + CARET_LEFT('\uf0D9'), + CARET_RIGHT('\uf0DA'), + CARET_SQUARE_ALT_DOWN('\uf150'), + CARET_SQUARE_ALT_LEFT('\uf191'), + CARET_SQUARE_ALT_RIGHT('\uf152'), + CARET_SQUARE_ALT_UP('\uf151'), + CARET_UP('\uf0D8'), + CART_ARROW_DOWN('\uf218'), + CART_PLUS('\uf217'), + CC('\uf20A'), + CC_AMEX('\uf1F3'), + CC_DISCOVER('\uf1F2'), + CC_MASTERCARD('\uf1F1'), + CC_PAYPAL('\uf1F4'), + CC_STRIPE('\uf1F5'), + CC_VISA('\uf1F0'), + CERTIFICATE('\uf0A3'), + CHAIN('\uf0C1'), + CHAIN_BROKEN('\uf127'), + CHECK('\uf00C'), + CHECK_CIRCLE('\uf058'), + CHECK_CIRCLE_ALT('\uf05D'), + CHECK_SQUARE('\uf14A'), + CHECK_SQUARE_ALT('\uf046'), + CHEVRON_CIRCLE_DOWN('\uf13A'), + CHEVRON_CIRCLE_LEFT('\uf137'), + CHEVRON_CIRCLE_RIGHT('\uf138'), + CHEVRON_CIRCLE_UP('\uf139'), + CHEVRON_DOWN('\uf078'), + CHEVRON_LEFT('\uf053'), + CHEVRON_RIGHT('\uf054'), + CHEVRON_UP('\uf077'), + CHILD('\uf1AE'), + CIRCLE('\uf111'), + CIRCLE_ALT('\uf10C'), + CIRCLE_ALT_NOTCH('\uf1CE'), + CIRCLE_THIN('\uf1DB'), + CLIPBOARD('\uf0EA'), + CLOCK_ALT('\uf017'), + CLOSE('\uf00D'), + CLOUD('\uf0C2'), + CLOUD_DOWNLOAD('\uf0ED'), + CLOUD_UPLOAD('\uf0EE'), + CNY('\uf157'), + CODE('\uf121'), + CODE_FORK('\uf126'), + CODEPEN('\uf1CB'), + COFFEE('\uf0F4'), + COG('\uf013'), + COGS('\uf085'), + COLUMNS('\uf0DB'), + COMMENT('\uf075'), + COMMENT_ALT('\uf0E5'), + COMMENTS('\uf086'), + COMMENTS_ALT('\uf0E6'), + COMPASS('\uf14E'), + COMPRESS('\uf066'), + CONNECTDEVELOP('\uf20E'), + COPY('\uf0C5'), + COPYRIGHT('\uf1F9'), + CREDIT_CARD('\uf09D'), + CROP('\uf125'), + CROSSHAIRS('\uf05B'), + CSS3('\uf13C'), + CUBE('\uf1B2'), + CUBES('\uf1B3'), + CUT('\uf0C4'), + CUTLERY('\uf0F5'), + DASHBOARD('\uf0E4'), + DASHCUBE('\uf210'), + DATABASE('\uf1C0'), + DEDENT('\uf03B'), + DELICIOUS('\uf1A5'), + DESKTOP('\uf108'), + DEVIANTART('\uf1BD'), + DIAMOND('\uf219'), + DIGG('\uf1A6'), + DOLLAR('\uf155'), + DOT_CIRCLE_ALT('\uf192'), + DOWNLOAD('\uf019'), + DRIBBBLE('\uf17D'), + DROPBOX('\uf16B'), + DRUPAL('\uf1A9'), + EDIT('\uf044'), + EJECT('\uf052'), + ELLIPSIS_H('\uf141'), + ELLIPSIS_V('\uf142'), + EMPIRE('\uf1D1'), + ENVELOPE('\uf0E0'), + ENVELOPE_ALT('\uf003'), + ENVELOPE_SQUARE('\uf199'), + ERASER('\uf12D'), + EUR('\uf153'), + EURO('\uf153'), + EXCHANGE('\uf0EC'), + EXCLAMATION('\uf12A'), + EXCLAMATION_CIRCLE('\uf06A'), + EXCLAMATION_TRIANGLE('\uf071'), + EXPAND('\uf065'), + EXTERNAL_LINK('\uf08E'), + EXTERNAL_LINK_SQUARE('\uf14C'), + EYE('\uf06E'), + EYE_SLASH('\uf070'), + EYEDROPPER('\uf1FB'), + FACEBOOK('\uf09A'), + FACEBOOK_F('\uf09A'), + FACEBOOK_ALTFFICIAL('\uf230'), + FACEBOOK_SQUARE('\uf082'), + FAST_BACKWARD('\uf049'), + FAST_FORWARD('\uf050'), + FAX('\uf1AC'), + FEMALE('\uf182'), + FIGHTER_JET('\uf0FB'), + FILE('\uf15B'), + FILE_ARCHIVE_ALT('\uf1C6'), + FILE_AUDIO_ALT('\uf1C7'), + FILE_CODE_ALT('\uf1C9'), + FILE_EXCEL_ALT('\uf1C3'), + FILE_IMAGE_ALT('\uf1C5'), + FILE_MOVIE_ALT('\uf1C8'), + FILE_ALT('\uf016'), + FILE_PDF_ALT('\uf1C1'), + FILE_PHOTO_ALT('\uf1C5'), + FILE_PICTURE_ALT('\uf1C5'), + FILE_POWERPOINT_ALT('\uf1C4'), + FILE_SOUND_ALT('\uf1C7'), + FILE_TEXT('\uf15C'), + FILE_TEXT_ALT('\uf0F6'), + FILE_VIDEO_ALT('\uf1C8'), + FILE_WORD_ALT('\uf1C2'), + FILE_ZIP_ALT('\uf1C6'), + FILES_ALT('\uf0C5'), + FILM('\uf008'), + FILTER('\uf0B0'), + FIRE('\uf06D'), + FIRE_EXTINGUISHER('\uf134'), + FLAG('\uf024'), + FLAG_CHECKERED('\uf11E'), + FLAG_ALT('\uf11D'), + FLASH('\uf0E7'), + FLASK('\uf0C3'), + FLICKR('\uf16E'), + FLOPPY_ALT('\uf0C7'), + FOLDER('\uf07B'), + FOLDER_ALT('\uf114'), + FOLDER_OPEN('\uf07C'), + FOLDER_OPEN_ALT('\uf115'), + FONT('\uf031'), + FORUMBEE('\uf211'), + FORWARD('\uf04E'), + FOURSQUARE('\uf180'), + FROWN_ALT('\uf119'), + FUTBOL_ALT('\uf1E3'), + GAMEPAD('\uf11B'), + GAVEL('\uf0E3'), + GBP('\uf154'), + GE('\uf1D1'), + GEAR('\uf013'), + GEARS('\uf085'), + GENDERLESS('\uf1DB'), + GIFT('\uf06B'), + GIT('\uf1D3'), + GIT_SQUARE('\uf1D2'), + GITHUB('\uf09B'), + GITHUB_ALT('\uf113'), + GITHUB_SQUARE('\uf092'), + GITTIP('\uf184'), + GLASS('\uf000'), + GLOBE('\uf0AC'), + GOOGLE('\uf1A0'), + GOOGLE_PLUS('\uf0D5'), + GOOGLE_PLUS_SQUARE('\uf0D4'), + GOOGLE_WALLET('\uf1EE'), + GRADUATION_CAP('\uf19D'), + GRATIPAY('\uf184'), + GROUP('\uf0C0'), + H_SQUARE('\uf0FD'), + HACKER_NEWS('\uf1D4'), + HAND_ALT_DOWN('\uf0A7'), + HAND_ALT_LEFT('\uf0A5'), + HAND_ALT_RIGHT('\uf0A4'), + HAND_ALT_UP('\uf0A6'), + HDD_ALT('\uf0A0'), + HEADER('\uf1DC'), + HEADPHONES('\uf025'), + HEART('\uf004'), + HEART_ALT('\uf08A'), + HEARTBEAT('\uf21E'), + HISTORY('\uf1DA'), + HOME('\uf015'), + HOSPITAL_ALT('\uf0F8'), + HOTEL('\uf236'), + HTML5('\uf13B'), + ILS('\uf20B'), + IMAGE('\uf03E'), + INBOX('\uf01C'), + INDENT('\uf03C'), + INFO('\uf129'), + INFO_CIRCLE('\uf05A'), + INR('\uf156'), + INSTAGRAM('\uf16D'), + INSTITUTION('\uf19C'), + IOXHOST('\uf208'), + ITALIC('\uf033'), + JOOMLA('\uf1AA'), + JPY('\uf157'), + JSFIDDLE('\uf1CC'), + KEY('\uf084'), + KEYBOARD_ALT('\uf11C'), + KRW('\uf159'), + LANGUAGE('\uf1AB'), + LAPTOP('\uf109'), + LASTFM('\uf202'), + LASTFM_SQUARE('\uf203'), + LEAF('\uf06C'), + LEANPUB('\uf212'), + LEGAL('\uf0E3'), + LEMON_ALT('\uf094'), + LEVEL_DOWN('\uf149'), + LEVEL_UP('\uf148'), + LIFE_BOUY('\uf1CD'), + LIFE_BUOY('\uf1CD'), + LIFE_RING('\uf1CD'), + LIFE_SAVER('\uf1CD'), + LIGHTBULB_ALT('\uf0EB'), + LINE_CHART('\uf201'), + LINK('\uf0C1'), + LINKEDIN('\uf0E1'), + LINKEDIN_SQUARE('\uf08C'), + LINUX('\uf17C'), + LIST('\uf03A'), + LIST_ALT('\uf022'), + LIST_OL('\uf0CB'), + LIST_UL('\uf0CA'), + LOCATION_ARROW('\uf124'), + LOCK('\uf023'), + LONG_ARROW_DOWN('\uf175'), + LONG_ARROW_LEFT('\uf177'), + LONG_ARROW_RIGHT('\uf178'), + LONG_ARROW_UP('\uf176'), + MAGIC('\uf0D0'), + MAGNET('\uf076'), + MAIL_FORWARD('\uf064'), + MAIL_REPLY('\uf112'), + MAIL_REPLY_ALL('\uf122'), + MALE('\uf183'), + MAP_MARKER('\uf041'), + MARS('\uf222'), + MARS_DOUBLE('\uf227'), + MARS_STROKE('\uf229'), + MARS_STROKE_H('\uf22B'), + MARS_STROKE_V('\uf22A'), + MAXCDN('\uf136'), + MEANPATH('\uf20C'), + MEDIUM('\uf23A'), + MEDKIT('\uf0FA'), + MEH_ALT('\uf11A'), + MERCURY('\uf223'), + MICROPHONE('\uf130'), + MICROPHONE_SLASH('\uf131'), + MINUS('\uf068'), + MINUS_CIRCLE('\uf056'), + MINUS_SQUARE('\uf146'), + MINUS_SQUARE_ALT('\uf147'), + MOBILE('\uf10B'), + MOBILE_PHONE('\uf10B'), + MONEY('\uf0D6'), + MOON_ALT('\uf186'), + MORTAR_BOARD('\uf19D'), + MOTORCYCLE('\uf21C'), + MUSIC('\uf001'), + NAVICON('\uf0C9'), + NEUTER('\uf22C'), + NEWSPAPER_ALT('\uf1EA'), + OPENID('\uf19B'), + OUTDENT('\uf03B'), + PAGELINES('\uf18C'), + PAINT_BRUSH('\uf1FC'), + PAPER_PLANE('\uf1D8'), + PAPER_PLANE_ALT('\uf1D9'), + PAPERCLIP('\uf0C6'), + PARAGRAPH('\uf1DD'), + PASTE('\uf0EA'), + PAUSE('\uf04C'), + PAW('\uf1B0'), + PAYPAL('\uf1ED'), + PENCIL('\uf040'), + PENCIL_SQUARE('\uf14B'), + PENCIL_SQUARE_ALT('\uf044'), + PHONE('\uf095'), + PHONE_SQUARE('\uf098'), + PHOTO('\uf03E'), + PICTURE_ALT('\uf03E'), + PIE_CHART('\uf200'), + PIED_PIPER('\uf1A7'), + PIED_PIPER_ALT('\uf1A8'), + PINTEREST('\uf0D2'), + PINTEREST_P('\uf231'), + PINTEREST_SQUARE('\uf0D3'), + PLANE('\uf072'), + PLAY('\uf04B'), + PLAY_CIRCLE('\uf144'), + PLAY_CIRCLE_ALT('\uf01D'), + PLUG('\uf1E6'), + PLUS('\uf067'), + PLUS_CIRCLE('\uf055'), + PLUS_SQUARE('\uf0FE'), + PLUS_SQUARE_ALT('\uf196'), + POWER_OFF('\uf011'), + PRINT('\uf02F'), + PUZZLE_PIECE('\uf12E'), + QQ('\uf1D6'), + QRCODE('\uf029'), + QUESTION('\uf128'), + QUESTION_CIRCLE('\uf059'), + QUOTE_LEFT('\uf10D'), + QUOTE_RIGHT('\uf10E'), + RA('\uf1D0'), + RANDOM('\uf074'), + REBEL('\uf1D0'), + RECYCLE('\uf1B8'), + REDDIT('\uf1A1'), + REDDIT_SQUARE('\uf1A2'), + REFRESH('\uf021'), + REMOVE('\uf00D'), + RENREN('\uf18B'), + REORDER('\uf0C9'), + REPEAT('\uf01E'), + REPLY('\uf112'), + REPLY_ALL('\uf122'), + RETWEET('\uf079'), + RMB('\uf157'), + ROAD('\uf018'), + ROCKET('\uf135'), + ROTATE_LEFT('\uf0E2'), + ROTATE_RIGHT('\uf01E'), + ROUBLE('\uf158'), + RSS('\uf09E'), + RSS_SQUARE('\uf143'), + RUB('\uf158'), + RUBLE('\uf158'), + RUPEE('\uf156'), + SAVE('\uf0C7'), + SCISSORS('\uf0C4'), + SEARCH('\uf002'), + SEARCH_MINUS('\uf010'), + SEARCH_PLUS('\uf00E'), + SELLSY('\uf213'), + SEND('\uf1D8'), + SEND_ALT('\uf1D9'), + SERVER('\uf233'), + SHARE('\uf064'), + SHARE_ALT('\uf1E0'), + SHARE_ALT_SQUARE('\uf1E1'), + SHARE_SQUARE('\uf14D'), + SHARE_SQUARE_ALT('\uf045'), + SHEKEL('\uf20B'), + SHEQEL('\uf20B'), + SHIELD('\uf132'), + SHIP('\uf21A'), + SHIRTSINBULK('\uf214'), + SHOPPING_CART('\uf07A'), + SIGN_IN('\uf090'), + SIGN_OUT('\uf08B'), + SIGNAL('\uf012'), + SIMPLYBUILT('\uf215'), + SITEMAP('\uf0E8'), + SKYATLAS('\uf216'), + SKYPE('\uf17E'), + SLACK('\uf198'), + SLIDERS('\uf1DE'), + SLIDESHARE('\uf1E7'), + SMILE_ALT('\uf118'), + SOCCER_BALL_ALT('\uf1E3'), + SORT('\uf0DC'), + SORT_ALPHA_ASC('\uf15D'), + SORT_ALPHA_DESC('\uf15E'), + SORT_AMOUNT_ASC('\uf160'), + SORT_AMOUNT_DESC('\uf161'), + SORT_ASC('\uf0DE'), + SORT_DESC('\uf0DD'), + SORT_DOWN('\uf0DD'), + SORT_NUMERIC_ASC('\uf162'), + SORT_NUMERIC_DESC('\uf163'), + SORT_UP('\uf0DE'), + SOUNDCLOUD('\uf1BE'), + SPACE_SHUTTLE('\uf197'), + SPINNER('\uf110'), + SPOON('\uf1B1'), + SPOTIFY('\uf1BC'), + SQUARE('\uf0C8'), + SQUARE_ALT('\uf096'), + STACK_EXCHANGE('\uf18D'), + STACK_OVERFLOW('\uf16C'), + STAR('\uf005'), + STAR_HALF('\uf089'), + STAR_HALF_EMPTY('\uf123'), + STAR_HALF_FULL('\uf123'), + STAR_HALF_ALT('\uf123'), + STAR_ALT('\uf006'), + STEAM('\uf1B6'), + STEAM_SQUARE('\uf1B7'), + STEP_BACKWARD('\uf048'), + STEP_FORWARD('\uf051'), + STETHOSCOPE('\uf0F1'), + STOP('\uf04D'), + STREET_VIEW('\uf21D'), + STRIKETHROUGH('\uf0CC'), + STUMBLEUPON('\uf1A4'), + STUMBLEUPON_CIRCLE('\uf1A3'), + SUBSCRIPT('\uf12C'), + SUBWAY('\uf239'), + SUITCASE('\uf0F2'), + SUN_ALT('\uf185'), + SUPERSCRIPT('\uf12B'), + SUPPORT('\uf1CD'), + TABLE('\uf0CE'), + TABLET('\uf10A'), + TACHOMETER('\uf0E4'), + TAG('\uf02B'), + TAGS('\uf02C'), + TASKS('\uf0AE'), + TAXI('\uf1BA'), + TENCENT_WEIBO('\uf1D5'), + TERMINAL('\uf120'), + TEXT_HEIGHT('\uf034'), + TEXT_WIDTH('\uf035'), + TH('\uf00A'), + TH_LARGE('\uf009'), + TH_LIST('\uf00B'), + THUMB_TACK('\uf08D'), + THUMBS_DOWN('\uf165'), + THUMBS_ALT_DOWN('\uf088'), + THUMBS_ALT_UP('\uf087'), + THUMBS_UP('\uf164'), + TICKET('\uf145'), + TIMES('\uf00D'), + TIMES_CIRCLE('\uf057'), + TIMES_CIRCLE_ALT('\uf05C'), + TINT('\uf043'), + TOGGLE_DOWN('\uf150'), + TOGGLE_LEFT('\uf191'), + TOGGLE_OFF('\uf204'), + TOGGLE_ON('\uf205'), + TOGGLE_RIGHT('\uf152'), + TOGGLE_UP('\uf151'), + TRAIN('\uf238'), + TRANSGENDER('\uf224'), + TRANSGENDER_ALT('\uf225'), + TRASH('\uf1F8'), + TRASH_ALT('\uf014'), + TREE('\uf1BB'), + TRELLO('\uf181'), + TROPHY('\uf091'), + TRUCK('\uf0D1'), + TRY('\uf195'), + TTY('\uf1E4'), + TUMBLR('\uf173'), + TUMBLR_SQUARE('\uf174'), + TURKISH_LIRA('\uf195'), + TWITCH('\uf1E8'), + TWITTER('\uf099'), + TWITTER_SQUARE('\uf081'), + UMBRELLA('\uf0E9'), + UNDERLINE('\uf0CD'), + UNDO('\uf0E2'), + UNIVERSITY('\uf19C'), + UNLINK('\uf127'), + UNLOCK('\uf09C'), + UNLOCK_ALT('\uf13E'), + UNSORTED('\uf0DC'), + UPLOAD('\uf093'), + USD('\uf155'), + USER('\uf007'), + USER_MD('\uf0F0'), + USER_PLUS('\uf234'), + USER_SECRET('\uf21B'), + USER_TIMES('\uf235'), + USERS('\uf0C0'), + VENUS('\uf221'), + VENUS_DOUBLE('\uf226'), + VENUS_MARS('\uf228'), + VIACOIN('\uf237'), + VIDEO_CAMERA('\uf03D'), + VIMEO_SQUARE('\uf194'), + VINE('\uf1CA'), + VK('\uf189'), + VOLUME_DOWN('\uf027'), + VOLUME_OFF('\uf026'), + VOLUME_UP('\uf028'), + WARNING('\uf071'), + WECHAT('\uf1D7'), + WEIBO('\uf18A'), + WEIXIN('\uf1D7'), + WHATSAPP('\uf232'), + WHEELCHAIR('\uf193'), + WIFI('\uf1EB'), + WINDOWS('\uf17A'), + WON('\uf159'), + WORDPRESS('\uf19A'), + WRENCH('\uf0AD'), + XING('\uf168'), + XING_SQUARE('\uf169'), + YAHOO('\uf19E'), + YELP('\uf1E9'), + YEN('\uf157'), + YOUTUBE('\uf167'), + YOUTUBE_PLAY('\uf16A'), + YOUTUBE_SQUARE('\uf166'); + + private final char ch; + + /** + * Creates a named Glyph mapped to the given character + * @param ch + */ + Glyph( char ch ) { + this.ch = ch; + } + + @Override + public char getChar() { + return ch; + } + }; + + /** + * Do not call this constructor directly - instead access the + * {@link FontAwesome.Glyph} public static enumeration method to create the glyph nodes), or + * use the {@link GlyphFontRegistry} class to get access. + * + * Note: Do not remove this public constructor since it is used by the service loader! + */ + public FontAwesome() { + this("http://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/fonts/fontawesome-webfont.ttf"); //$NON-NLS-1$ + } + + /** + * Creates a new FontAwesome instance which uses the provided font source. + * @param url + */ + public FontAwesome(String url){ + super(fontName, 14, url, true); + registerAll(Arrays.asList(Glyph.values())); + } + + /** + * Creates a new FontAwesome instance which uses the provided font source. + * @param is + */ + public FontAwesome(InputStream is){ + super(fontName, 14, is, true); + registerAll(Arrays.asList(Glyph.values())); + } + +} diff --git a/src/org/controlsfx/glyphfont/Glyph.java b/src/org/controlsfx/glyphfont/Glyph.java new file mode 100644 index 0000000000000000000000000000000000000000..8bb8c729e3ecc6b088b85a3ad2a202182ea13e42 --- /dev/null +++ b/src/org/controlsfx/glyphfont/Glyph.java @@ -0,0 +1,350 @@ +/** + * Copyright (c) 2013, 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.glyphfont; + +import java.util.Optional; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.paint.*; +import javafx.scene.text.Font; + +import org.controlsfx.control.action.Action; +import org.controlsfx.tools.Duplicatable; + +/** + * Represents one glyph from the font. + * The glyph is actually a label showing one character from the specified font. It can be used as 'graphic' on any UI + * control or {@link Action}. It can also directly be used in FXML. + * + * Examples: + * + * <pre>{@code + * new Button("", new Glyph("FontAwesome", "BEER")) + * }</pre> + * + * <pre>{@code + * new Button("", new Glyph("FontAwesome", FontAwesome.Glyph.BEER)) + * }</pre> + * + * Thy Glyph-Class also offers a fluent API to customize the look of the Glyph. + * For example, you can set the color {@link #color(javafx.scene.paint.Color)} or + * also add effects such as {@link #useHoverEffect()} + * + * <p>An ability to retrieve glyph node by combination of font name and glyph name + * extends to the {@link org.controlsfx.control.action.ActionProxy} graphic attribute, where the "font>" + * prefix should be used. For more information see {@link org.controlsfx.control.action.ActionProxy}. + * + */ +public class Glyph extends Label implements Duplicatable<Glyph> { + + /*************************************************************************** + * * + * Static creators * + * * + **************************************************************************/ + + /** + * Retrieve glyph by font name and glyph name using one string + * where font name an glyph name are separated by pipe. + * + * @param fontAndGlyph The font and glyph separated by a pipe. Example: "FontAwesome|STAR" + * @return A instance of a Glyph node + */ + public static Glyph create(String fontAndGlyph) { + String[] args = fontAndGlyph.split("\\|"); //$NON-NLS-1$ + return new Glyph(args[0], args[1]); + } + + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + public final static String DEFAULT_CSS_CLASS = "glyph-font"; //$NON-NLS-1$ + public final static String STYLE_GRADIENT = "gradient"; //$NON-NLS-1$ + public final static String STYLE_HOVER_EFFECT = "hover-effect"; //$NON-NLS-1$ + + private final ObjectProperty<Object> icon = new SimpleObjectProperty<>(); + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Empty Constructor (used by FXML) + */ + public Glyph(){ + getStyleClass().add(DEFAULT_CSS_CLASS); + + icon.addListener(x -> updateIcon()); + fontProperty().addListener(x -> updateIcon()); + } + + /** + * Creates a new Glyph + * @param fontFamily The family name of the font. Example: "FontAwesome" + * @param unicode The Unicode character of the glyph + */ + public Glyph(String fontFamily, char unicode) { + this(); + setFontFamily(fontFamily); + setTextUnicode(unicode); + } + + /** + * Creates a new Glyph + * @param fontFamily The family name of the font. Example: "FontAwesome" + * @param icon The icon - which can be the name (String) or Enum value. + * Example: FontAwesome.Glyph.BEER + */ + public Glyph(String fontFamily, Object icon) { + this(); + setFontFamily(fontFamily); + setIcon(icon); + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** + * Sets the glyph icon font family + * @param fontFamily A font family name + * @return Returns this instance for fluent API + */ + public Glyph fontFamily(String fontFamily){ + setFontFamily(fontFamily); + return this; + } + + /** + * Sets the glyph color + * @param color + * @return Returns this instance for fluent API + */ + public Glyph color(Color color){ + setColor(color); + return this; + } + + /** + * Sets glyph size + * @param size + * @return Returns this instance for fluent API + */ + public Glyph size(double size) { + setFontSize(size); + return this; + } + + /** + * Sets glyph size using size factor based on default font size + * @param factor + * @return Returns this instance for fluent API + */ + public Glyph sizeFactor(int factor) { + Optional.ofNullable(GlyphFontRegistry.font(getFont().getFamily())).ifPresent( glyphFont ->{ + setFontSize(glyphFont.getDefaultSize()* (factor < 1? 1: factor)); + }); + return this; + } + + + + /** + * Adds the hover effect style + * @return Returns this instance for fluent API + */ + public Glyph useHoverEffect(){ + this.getStyleClass().add(Glyph.STYLE_HOVER_EFFECT); + return this; + } + + /** + * Adds the gradient effect style + * @return Returns this instance for fluent API + */ + public Glyph useGradientEffect(){ + + if(getTextFill() instanceof Color){ + Color currentColor = (Color)getTextFill(); + + /* + TODO + Do this in code: + -fx-text-fill: linear-gradient(to bottom, derive(-fx-text-fill,20%) 10%, derive(-fx-text-fill,-40%) 80%); + */ + Stop[] stops = new Stop[] { new Stop(0, Color.BLACK), new Stop(1, currentColor)}; + LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops); + setTextFill(lg1); + } + + this.getStyleClass().add(Glyph.STYLE_GRADIENT); + return this; + } + + + /** + * Allows glyph duplication. Since in the JavaFX scenegraph it is not possible to insert the same + * {@link Node} in multiple locations at the same time, this method allows for glyph reuse in several places + */ + @Override public Glyph duplicate() { + Paint color = getTextFill(); + Object icon = getIcon(); + ObservableList<String> classes = getStyleClass(); + return new Glyph(){{ + setIcon(icon); + setTextFill(color); + getStyleClass().addAll(classes); + }} + .fontFamily(getFontFamily()) + .size(getFontSize()); + } + + /*************************************************************************** + * * + * Properties * + * * + **************************************************************************/ + + /** + * Sets the font family of this glyph + * Font size is reset to default glyph font size + */ + public void setFontFamily(String family){ + if( !getFont().getFamily().equals(family)){ + Optional.ofNullable(GlyphFontRegistry.font(family)).ifPresent( glyphFont -> { + glyphFont.ensureFontIsLoaded(); // Make sure font is loaded + Font newFont = Font.font(family, glyphFont.getDefaultSize()); // Reset to default font size + setFont(newFont); + }); + } + } + + /** + * Gets the font family of this glyph + */ + public String getFontFamily(){ + return getFont().getFamily(); + } + + /** + * Sets the font size of this glyph + */ + public void setFontSize(double size){ + Font newFont = Font.font(getFont().getFamily(), size); + setFont(newFont); + } + + /** + * Gets the font size of this glyph + */ + public double getFontSize(){ + return getFont().getSize(); + } + + /** + * Set the Color of this Glyph + */ + public void setColor(Color color){ + setTextFill(color); + } + + /** + * The icon name property. + * + * This must either be a Glyph-Name (either string or enum value) known by the GlyphFontRegistry. + * Alternatively, you can directly submit a unicode character here. + */ + public ObjectProperty<Object> iconProperty(){ + return icon; + } + + /** + * Set the icon to display. + * @param iconValue This can either be the Glyph-Name, Glyph-Enum Value or a unicode character representing the sign. + */ + public void setIcon(Object iconValue){ + icon.set(iconValue); + } + + public Object getIcon(){ + return icon.get(); + } + + /*************************************************************************** + * * + * Private methods * + * * + **************************************************************************/ + + + /** + * This updates the text with the correct unicode value + * so that the desired icon is displayed. + */ + private void updateIcon(){ + + Object iconValue = getIcon(); + + if(iconValue != null) { + if(iconValue instanceof Character){ + setTextUnicode((Character)iconValue); + }else { + GlyphFont glyphFont = GlyphFontRegistry.font(getFontFamily()); + if (glyphFont != null) { + String name = iconValue.toString(); + Character unicode = glyphFont.getCharacter(name); + if (unicode != null) { + setTextUnicode(unicode); + } else { + // Could not find a icon with this name + setText(name); + } + } + } + } + } + + /** + * Sets the given char as text + * @param unicode + */ + private void setTextUnicode(char unicode){ + setText(String.valueOf(unicode)); + } +} \ No newline at end of file diff --git a/src/org/controlsfx/glyphfont/GlyphFont.java b/src/org/controlsfx/glyphfont/GlyphFont.java new file mode 100644 index 0000000000000000000000000000000000000000..ea355cd0f9387ab5b5043d2e03ceb3ef0668ff13 --- /dev/null +++ b/src/org/controlsfx/glyphfont/GlyphFont.java @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.glyphfont; + +import com.sun.javafx.css.StyleManager; +import javafx.scene.text.Font; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents a glyph font, which can be loaded locally or from a specified URL. + * {@link Glyph}s can be created easily using specified character defined in the + * font. For example, \uf013 in FontAwesome is used to represent + * a gear icon. + * + * <p>To simplify glyph customization, methods can be chained, for example: + * + * <pre> + * Glyph glyph = fontAwesome.create('\uf013').size(28).color(Color.RED); //GEAR + * </pre> + * + * <p>Here's a screenshot of two font packs being used to render images into + * JavaFX Button controls: + * + * <br> + * <center><img src="glyphFont.png" alt="Screenshot of GlyphFont"></center> + */ +public class GlyphFont { + + static { + StyleManager.getInstance().addUserAgentStylesheet( + GlyphFont.class.getResource("glyphfont.css").toExternalForm()); //$NON-NLS-1$ + } + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + private final Map<String, Character> namedGlyphs = new HashMap<>(); + private final Runnable fontLoader; + private final String fontName; + private final double defaultSize; + + private boolean fontLoaded = false; + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * Loads glyph font from specified {@link InputStream} + * @param fontName glyph font name + * @param defaultSize default font size + * @param in input stream to load the font from + */ + public GlyphFont( String fontName, int defaultSize, final InputStream in) { + this(fontName, defaultSize, in, false); + } + + /** + * Load glyph font from specified URL. + * Example for a local file: + * "file:///C:/Users/Bob/Fonts/icomoon.ttf" + * "file:///Users/Bob/Fonts/icomoon.ttf" + * + * @param fontName glyph font name + * @param defaultSize default font size + * @param urlStr A URL to load the font from + */ + public GlyphFont( String fontName, int defaultSize, final String urlStr) { + this(fontName, defaultSize, urlStr, false); + } + + /** + * Loads glyph font from specified {@link InputStream} + * @param fontName glyph font name + * @param defaultSize default font size + * @param in input stream to load the font from + * @param lazyLoad If true, the font will only be loaded when accessed + */ + public GlyphFont( String fontName, int defaultSize, final InputStream in, boolean lazyLoad) { + this(fontName, defaultSize, () -> { + Font.loadFont(in, -1); + }, lazyLoad); + } + + /** + * Load glyph font from specified URL. + * Example for a local file: + * "file:///C:/Users/Bob/Fonts/icomoon.ttf" + * "file:///Users/Bob/Fonts/icomoon.ttf" + * + * @param fontName glyph font name + * @param defaultSize default font size + * @param urlStr A URL to load the font from + * @param lazyLoad If true, the font will only be loaded when accessed + */ + public GlyphFont( String fontName, int defaultSize, final String urlStr, boolean lazyLoad) { + this(fontName, defaultSize, () -> { + Font.loadFont(urlStr, -1); + }, lazyLoad); + } + + /** + * Creates a GlyphFont + * @param fontName + * @param defaultSize + * @param fontLoader + * @param lazyLoad + */ + private GlyphFont(String fontName, int defaultSize, Runnable fontLoader, boolean lazyLoad){ + this.fontName = fontName; + this.defaultSize = defaultSize; + this.fontLoader = fontLoader; + + if(!lazyLoad){ + ensureFontIsLoaded(); + } + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** + * Returns font name + * @return font name + */ + public String getName() { + return fontName; + } + + /** + * Returns the default font size + * @return default font size + */ + public double getDefaultSize() { + return defaultSize; + } + + + /** + * Creates an instance of {@link Glyph} using specified font character + * @param character font character + * @return instance of {@link Glyph} + */ + public Glyph create(char character) { + return new Glyph(fontName, character); + } + + /** + * Creates an instance of {@link Glyph} using glyph name + * @param glyphName glyph name + * @return glyph by its name or null if name is not found + */ + public Glyph create(String glyphName) { + return new Glyph(fontName, glyphName); + } + + /** + * Creates an instance of {@link Glyph} using a known Glyph enum value + * @param glyph + */ + public Glyph create(Enum<?> glyph) { + return new Glyph(fontName, glyph); + } + + /** + * Returns the character code which is mapped to this Name. + * If no match is found, NULL is returned. + * @param glyphName + */ + public Character getCharacter(String glyphName){ + return namedGlyphs.get(glyphName.toUpperCase()); + } + + + /** + * Registers all given characters with their name. + * @param namedCharacters + */ + public void registerAll(Iterable<? extends INamedCharacter> namedCharacters){ + for (INamedCharacter e: namedCharacters) { + register(e.name(), e.getChar()); + } + } + + /** + * Registers the given name-character mapping + * @param name + * @param character + */ + public void register(String name, Character character){ + namedGlyphs.put(name.toUpperCase(), character); + } + + /*************************************************************************** + * * + * Internal methods * + * * + **************************************************************************/ + + /** + * Ensures that the font is loaded + */ + synchronized void ensureFontIsLoaded(){ + if ( !fontLoaded ) { + fontLoader.run(); + fontLoaded = true; + } + } +} diff --git a/src/org/controlsfx/glyphfont/GlyphFontRegistry.java b/src/org/controlsfx/glyphfont/GlyphFontRegistry.java new file mode 100644 index 0000000000000000000000000000000000000000..cc9d433d561308f6d09b5e18b7d6f0a510d84c16 --- /dev/null +++ b/src/org/controlsfx/glyphfont/GlyphFontRegistry.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2013,2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.glyphfont; + + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; + +/** + * The glyph font registry automatically registers available fonts using a + * {@link ServiceLoader} facility, however it is also possible to register + * glyph fonts manually using the provided + * {@link GlyphFontRegistry#register(GlyphFont)} method. + * + * <p>Once registered, fonts can be requested by name using the + * {@link GlyphFontRegistry#font(String)} method. + * + * Please refer to the {@link GlyphFont} documentation + * to learn how to use a font. + * + */ +public final class GlyphFontRegistry { + + /*************************************************************************** + * * + * Private fields * + * * + **************************************************************************/ + + private static Map<String, GlyphFont> fontMap = new HashMap<>(); + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + static { + // find all classes that implement GlyphFont and register them now + ServiceLoader<GlyphFont> loader = ServiceLoader.load(GlyphFont.class); + for (GlyphFont font : loader) { + GlyphFontRegistry.register(font); + } + } + + /** + * Private constructor since static class + */ + private GlyphFontRegistry() { + // no-op + } + + /*************************************************************************** + * * + * Public API * + * * + **************************************************************************/ + + /** + * Registers the specified font as default GlyphFont + * @param familyName The name of this font. + * @param uri The location where it can be loaded from. + * @param defaultSize The default font size + */ + public static void register(String familyName, String uri, int defaultSize){ + register(new GlyphFont(familyName, defaultSize, uri)); + } + + /** + * Registers the specified font as default GlyphFont + * @param familyName The name of this font. + * @param in Inputstream of the font data + * @param defaultSize The default font size + */ + public static void register(String familyName, InputStream in, int defaultSize){ + register(new GlyphFont(familyName, defaultSize, in)); + } + + /** + * Registers the specified font + * @param font + */ + public static void register( GlyphFont font ) { + if (font != null ) { + fontMap.put( font.getName(), font ); + } + } + + /** + * Retrieve font by its family name + * @param familyName family name of the font + * @return font or null if not found + */ + public static GlyphFont font( String familyName ) { + GlyphFont font = fontMap.get(familyName); + if(font != null) { + font.ensureFontIsLoaded(); + } + return font; + } +} diff --git a/src/org/controlsfx/glyphfont/INamedCharacter.java b/src/org/controlsfx/glyphfont/INamedCharacter.java new file mode 100644 index 0000000000000000000000000000000000000000..60b7069d174edb475fe6449ba102ddd5da708b4f --- /dev/null +++ b/src/org/controlsfx/glyphfont/INamedCharacter.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.glyphfont; + +/** + * Represents a named character. + * This interface is usually implemented by a Enum + * which holds all characters of a specific font. + */ +public interface INamedCharacter { + /** + * Gets the name of this character + */ + String name(); + + /** + * Gets the character value + */ + char getChar(); +} diff --git a/src/org/controlsfx/glyphfont/glyphfont.css b/src/org/controlsfx/glyphfont/glyphfont.css new file mode 100644 index 0000000000000000000000000000000000000000..2803ef35107840434a1dc20c0ced03f8c2e0cf28 --- /dev/null +++ b/src/org/controlsfx/glyphfont/glyphfont.css @@ -0,0 +1,16 @@ + +.glyph-font { + +} + +.glyph-font.gradient{ + -fx-effect: innershadow( three-pass-box , derive(-fx-text-fill,-70%) , 0.1em, 0.0 , 0.07em, 0.07em ); +} + +.glyph-font.hover-effect:hover{ + -fx-effect: dropshadow( three-pass-box , derive(-fx-text-fill,0%) , 0.01em, 0.0 , 0, 0); +} + +.glyph-font.hover-effect:selected{ + -fx-effect: dropshadow( three-pass-box , derive(-fx-text-fill,0%) , 0.01em, 0.0 , 0, 0); +} \ No newline at end of file diff --git a/src/org/controlsfx/glyphfont/package-info.java b/src/org/controlsfx/glyphfont/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a4cee1649394f4789de25cbde2a6127c2aef1da1 --- /dev/null +++ b/src/org/controlsfx/glyphfont/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing a number of useful code related to loading and using + * font packs whose characters are actually images. + */ +package org.controlsfx.glyphfont; \ No newline at end of file diff --git a/src/org/controlsfx/property/BeanProperty.java b/src/org/controlsfx/property/BeanProperty.java new file mode 100644 index 0000000000000000000000000000000000000000..341525347e98d8e8a957267f5f36717277dd6473 --- /dev/null +++ b/src/org/controlsfx/property/BeanProperty.java @@ -0,0 +1,208 @@ +/** + * Copyright (c) 2013, 2015, 2016 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property; + +import java.beans.FeatureDescriptor; +import java.beans.PropertyDescriptor; +import java.beans.PropertyVetoException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Optional; + +import org.controlsfx.control.PropertySheet; +import org.controlsfx.control.PropertySheet.Item; +import org.controlsfx.property.editor.PropertyEditor; + +import impl.org.controlsfx.i18n.Localization; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.Alert; + +/** + * A convenience class for creating a {@link Item} for use in the + * {@link PropertySheet} control based on a property belonging to a + * JavaBean - simply provide a {@link PropertyDescriptor} and the rest will be + * taken care of automatically. + * + * @see Item + * @see PropertySheet + * @see PropertyDescriptor + */ +public class BeanProperty implements PropertySheet.Item { + + /** + * Unique identifier to provide a custom category label within + * {@link PropertySheet.Item#getCategory()}. + * + * How to use it: with a PropertyDescriptor, provide the custom category + * through a a named attribute + * {@link FeatureDescriptor#setValue(String, Object)}. + * + * <pre> + * final PropertyDescriptor propertyDescriptor = new PropertyDescriptor("yourProperty", YourBean.class); + * propertyDescriptor.setDisplayName("Your Display Name"); + * propertyDescriptor.setShortDescription("Your explanation about this property."); + * // then provide a custom category + * propertyDescriptor.setValue(BeanProperty.CATEGORY_LABEL_KEY, "Your custom category"); + * </pre> + */ + public static final String CATEGORY_LABEL_KEY = "propertysheet.item.category.label"; + + private final Object bean; + private final PropertyDescriptor beanPropertyDescriptor; + private final Method readMethod; + private boolean editable = true; + private Optional<ObservableValue<? extends Object>> observableValue = Optional.empty(); + + public BeanProperty(final Object bean, final PropertyDescriptor propertyDescriptor) { + this.bean = bean; + this.beanPropertyDescriptor = propertyDescriptor; + this.readMethod = propertyDescriptor.getReadMethod(); + if (this.beanPropertyDescriptor.getWriteMethod() == null) { + this.setEditable(false); + } + + this.findObservableValue(); + } + + /** {@inheritDoc} */ + @Override public String getName() { + return this.beanPropertyDescriptor.getDisplayName(); + } + + /** {@inheritDoc} */ + @Override public String getDescription() { + return this.beanPropertyDescriptor.getShortDescription(); + } + + /** {@inheritDoc} */ + @Override public Class<?> getType() { + return this.beanPropertyDescriptor.getPropertyType(); + } + + /** {@inheritDoc} */ + @Override public Object getValue() { + try { + return this.readMethod.invoke(this.bean); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + return null; + } + } + + /** {@inheritDoc} */ + @Override public void setValue(final Object value) { + final Method writeMethod = this.beanPropertyDescriptor.getWriteMethod(); + if ( writeMethod != null ) { + try { + writeMethod.invoke(this.bean, value); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + } catch (final Throwable e) { + if (e instanceof PropertyVetoException) { + final Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(Localization.localize(Localization.asKey("bean.property.change.error.title")));//$NON-NLS-1$ + alert.setHeaderText(Localization.localize(Localization.asKey("bean.property.change.error.masthead")));//$NON-NLS-1$ + alert.setContentText(e.getLocalizedMessage()); + alert.showAndWait(); + } else { + throw e; + } + } + } + } + + /** {@inheritDoc} */ + @Override public String getCategory() { + String category = (String) this.beanPropertyDescriptor.getValue(BeanProperty.CATEGORY_LABEL_KEY); + + // fall back to default behavior if there is no category provided. + if (category == null) { + category = Localization.localize(Localization.asKey(this.beanPropertyDescriptor.isExpert() + ? "bean.property.category.expert" : "bean.property.category.basic")); //$NON-NLS-1$ //$NON-NLS-2$ + } + return category; + } + + /** + * @return The object passed in to the constructor of the BeanProperty. + */ + public Object getBean() { + return this.bean; + } + + /** + * @return The {@link PropertyDescriptor} passed in to the constructor of + * the BeanProperty. + */ + public PropertyDescriptor getPropertyDescriptor() { + return this.beanPropertyDescriptor; + } + + /** {@inheritDoc} */ + @SuppressWarnings({ "unchecked" }) + @Override public Optional<Class<? extends PropertyEditor<?>>> getPropertyEditorClass() { + + if ((this.beanPropertyDescriptor.getPropertyEditorClass() != null) && + PropertyEditor.class.isAssignableFrom(this.beanPropertyDescriptor.getPropertyEditorClass())) { + + return Optional.of((Class<PropertyEditor<?>>)this.beanPropertyDescriptor.getPropertyEditorClass()); + } + + return Item.super.getPropertyEditorClass(); + } + + /** {@inheritDoc} */ + @Override public boolean isEditable() { + return this.editable; + } + + /** + * @param editable Whether this property should be editable in the PropertySheet. + */ + public void setEditable(final boolean editable) { + this.editable = editable; + } + + /** {@inheritDoc} */ + @Override public Optional<ObservableValue<? extends Object>> getObservableValue() { + return this.observableValue; + } + + private void findObservableValue() { + try { + final String propName = this.beanPropertyDescriptor.getName() + "Property"; + final Method m = this.getBean().getClass().getMethod(propName); + final Object val = m.invoke(this.getBean()); + if ((val != null) && (val instanceof ObservableValue)) { + this.observableValue = Optional.of((ObservableValue<?>) val); + } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + //Logger.getLogger(BeanProperty.class.getName()).log(Level.SEVERE, null, ex); + // ignore it... + } + } +} diff --git a/src/org/controlsfx/property/BeanPropertyUtils.java b/src/org/controlsfx/property/BeanPropertyUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b88093bc5eec9c3a8a933b853a9e79b9685d9224 --- /dev/null +++ b/src/org/controlsfx/property/BeanPropertyUtils.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.util.function.Predicate; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; + +import org.controlsfx.control.PropertySheet; +import org.controlsfx.control.PropertySheet.Item; + +/** + * Convenience utility class for creating {@link PropertySheet} instances based + * on a JavaBean. + */ +public final class BeanPropertyUtils { + + private BeanPropertyUtils() { + // no op + } + + /** + * Given a JavaBean, this method will return a list of {@link Item} intances, + * which may be directly placed inside a {@link PropertySheet} (via its + * {@link PropertySheet#getItems() items list}. + * <p> + * This method will not return read-only properties. + * + * @param bean The JavaBean that should be introspected and be editable via + * a {@link PropertySheet}. + * @return A list of {@link Item} instances representing the properties of the + * JavaBean. + */ + public static ObservableList<Item> getProperties(final Object bean) { + return getProperties(bean, (p) -> {return true;} ); + } + + /** + * Given a JavaBean, this method will return a list of {@link Item} intances, + * which may be directly placed inside a {@link PropertySheet} (via its + * {@link PropertySheet#getItems() items list}. + * + * @param bean The JavaBean that should be introspected and be editable via + * a {@link PropertySheet}. + * @param test Predicate to test whether the property should be included in the + * list of results. + * @return A list of {@link Item} instances representing the properties of the + * JavaBean. + */ + public static ObservableList<Item> getProperties(final Object bean, Predicate<PropertyDescriptor> test) { + ObservableList<Item> list = FXCollections.observableArrayList(); + try { + BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class); + for (PropertyDescriptor p : beanInfo.getPropertyDescriptors()) { + if (test.test(p)) { + list.add(new BeanProperty(bean, p)); + } + } + } catch (IntrospectionException e) { + e.printStackTrace(); + } + + return list; + } + +} diff --git a/src/org/controlsfx/property/editor/AbstractObjectField.java b/src/org/controlsfx/property/editor/AbstractObjectField.java new file mode 100644 index 0000000000000000000000000000000000000000..d6f394135c09736515675b4a0bc65c456b6a9487 --- /dev/null +++ b/src/org/controlsfx/property/editor/AbstractObjectField.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property.editor; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.Cursor; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; + +import org.controlsfx.control.textfield.CustomTextField; + +// package-private for now... +abstract class AbstractObjectField<T> extends HBox { + + //TODO: Replace with CSS + private static final Image image = new Image(AbstractObjectField.class.getResource("/org/controlsfx/control/open-editor.png").toExternalForm()); //$NON-NLS-1$ + + private final CustomTextField textField = new CustomTextField(); + + private ObjectProperty<T> objectProperty = new SimpleObjectProperty<>(); + + public AbstractObjectField() { + super(1); + textField.setEditable(false); + textField.setFocusTraversable(false); + + StackPane button = new StackPane(new ImageView(image)); + button.setCursor(Cursor.DEFAULT); + + button.setOnMouseReleased(e -> { + if ( MouseButton.PRIMARY == e.getButton() ) { + final T result = edit(objectProperty.get()); + if (result != null) { + objectProperty.set(result); + } + } + }); + + textField.setRight(button); + getChildren().add(textField); + HBox.setHgrow(textField, Priority.ALWAYS); + + objectProperty.addListener((o, oldValue, newValue) -> textProperty().set(objectToString(newValue))); + } + + protected StringProperty textProperty() { + return textField.textProperty(); + } + + public ObjectProperty<T> getObjectProperty() { + return objectProperty; + } + + protected String objectToString(T object) { + return object == null ? "" : object.toString(); //$NON-NLS-1$ + } + + protected abstract Class<T> getType(); + + protected abstract T edit(T object); +} diff --git a/src/org/controlsfx/property/editor/AbstractPropertyEditor.java b/src/org/controlsfx/property/editor/AbstractPropertyEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..5a3cc8020fdfd7dce5927df37726dfe53318f9a6 --- /dev/null +++ b/src/org/controlsfx/property/editor/AbstractPropertyEditor.java @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property.editor; + +import javafx.beans.value.ObservableValue; +import javafx.scene.Node; + +import org.controlsfx.control.PropertySheet.Item; + +/** + * An abstract implementation of the {@link PropertyEditor} interface. + * + * @param <T> The type of the property being edited. + * @param <C> The type of Node that is used to edit this property. + */ +public abstract class AbstractPropertyEditor<T, C extends Node> implements PropertyEditor<T> { + + /************************************************************************** + * + * Private fields + * + **************************************************************************/ + + private final Item property; + private final C control; + private boolean suspendUpdate; + + + /************************************************************************** + * + * Constructors + * + **************************************************************************/ + + /** + * Creates an editable AbstractPropertyEditor instance for the given property + * using the given editing control. + * + * @param property The property that the instance is responsible for editing. + * @param control The control that is responsible for editing the property. + */ + public AbstractPropertyEditor(Item property, C control) { + this(property, control, ! property.isEditable()); + } + + /** + * Creates an AbstractPropertyEditor instance for the given property + * using the given editing control. It may be read-only or editable, based + * on the readonly boolean parameter being true or false. + * + * @param property The property that the instance is responsible for editing. + * @param control The control that is responsible for editing the property. + * @param readonly Specifies whether the editor should allow input or not. + */ + public AbstractPropertyEditor(Item property, C control, boolean readonly) { + this.control = control; + this.property = property; + if (! readonly) { + getObservableValue().addListener((ObservableValue<? extends Object> o, Object oldValue, Object newValue) -> { + if (! suspendUpdate) { + suspendUpdate = true; + AbstractPropertyEditor.this.property.setValue(getValue()); + suspendUpdate = false; + } + }); + + if (property.getObservableValue().isPresent()) { + property.getObservableValue().get().addListener((ObservableValue<? extends Object> o, Object oldValue, Object newValue) -> { + if (! suspendUpdate) { + suspendUpdate = true; + AbstractPropertyEditor.this.setValue((T) property.getValue()); + suspendUpdate = false; + } + }); + } + + } + } + + + + /************************************************************************** + * + * Public API + * + **************************************************************************/ + + /** + * Returns an {@link ObservableValue} of the property that this property + * editor is responsible for editing. This is the editor's value, e.g. a + * TextField's textProperty(). + */ + protected abstract ObservableValue<T> getObservableValue(); + + /** + * Returns the property that this property editor is responsible for editing. + */ + public final Item getProperty() { + return property; + } + + /** + * {@inheritDoc} + */ + @Override public C getEditor() { + return control; + } + + /** + * {@inheritDoc} + */ + @Override public T getValue() { + return getObservableValue().getValue(); + } +} diff --git a/src/org/controlsfx/property/editor/DefaultPropertyEditorFactory.java b/src/org/controlsfx/property/editor/DefaultPropertyEditorFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..36b483b67d60fff2cee5835e42a5564a9712c424 --- /dev/null +++ b/src/org/controlsfx/property/editor/DefaultPropertyEditorFactory.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property.editor; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Optional; + +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import javafx.scene.text.Font; +import javafx.util.Callback; + +import org.controlsfx.control.PropertySheet; +import org.controlsfx.control.PropertySheet.Item; + +/** + * A default implementation of the {@link Callback} type required by the + * {@link PropertySheet} + * {@link PropertySheet#propertyEditorFactory() property editor factory}. By + * default this is the implementation used by PropertySheet, but developers may + * choose to provide their own, or more likely, extend this implementation + * and override the {@link DefaultPropertyEditorFactory#call(org.controlsfx.control.PropertySheet.Item) } method to + * add in support for additional editor types. + * + * @see PropertySheet + */ +public class DefaultPropertyEditorFactory implements Callback<Item, PropertyEditor<?>> { + + @Override public PropertyEditor<?> call(Item item) { + Class<?> type = item.getType(); + + //TODO: add support for char and collection editors + + if (item.getPropertyEditorClass().isPresent()) { + Optional<PropertyEditor<?>> ed = Editors.createCustomEditor(item); + if (ed.isPresent()) return ed.get(); + } + + if (/*type != null &&*/ type == String.class) { + return Editors.createTextEditor(item); + } + + if (/*type != null &&*/ isNumber(type)) { + return Editors.createNumericEditor(item); + } + + if (/*type != null &&*/(type == boolean.class || type == Boolean.class)) { + return Editors.createCheckEditor(item); + } + + if (/*type != null &&*/type == LocalDate.class) { + return Editors.createDateEditor(item); + } + + if (/*type != null &&*/type == Color.class || type == Paint.class) { + return Editors.createColorEditor(item); + } + + if (type != null && type.isEnum()) { + return Editors.createChoiceEditor(item, Arrays.<Object>asList(type.getEnumConstants())); + } + + if (/*type != null &&*/type == Font.class) { + return Editors.createFontEditor(item); + } + + return null; + } + + private static Class<?>[] numericTypes = new Class[]{ + byte.class, Byte.class, + short.class, Short.class, + int.class, Integer.class, + long.class, Long.class, + float.class, Float.class, + double.class, Double.class, + BigInteger.class, BigDecimal.class + }; + + // there should be better ways to do this + private static boolean isNumber(Class<?> type) { + if ( type == null ) return false; + for (Class<?> cls : numericTypes) { + if ( type == cls ) return true; + } + return false; + } +} diff --git a/src/org/controlsfx/property/editor/Editors.java b/src/org/controlsfx/property/editor/Editors.java new file mode 100644 index 0000000000000000000000000000000000000000..c742d49d597a9536a95969f33bcbd621d6424b25 --- /dev/null +++ b/src/org/controlsfx/property/editor/Editors.java @@ -0,0 +1,232 @@ +/** + * Copyright (c) 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property.editor; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.time.LocalDate; +import java.util.Collection; +import java.util.Optional; + +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.ComboBox; +import javafx.scene.control.DatePicker; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputControl; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; + +import org.controlsfx.control.PropertySheet; +import org.controlsfx.control.PropertySheet.Item; +import org.controlsfx.dialog.FontSelectorDialog; + +@SuppressWarnings("deprecation") +public class Editors { + + private Editors() { + // no op + } + + public static final PropertyEditor<?> createTextEditor( Item property ) { + + return new AbstractPropertyEditor<String, TextField>(property, new TextField()) { + + { enableAutoSelectAll(getEditor()); } + + @Override protected StringProperty getObservableValue() { + return getEditor().textProperty(); + } + + @Override public void setValue(String value) { + getEditor().setText(value); + } + }; + } + + @SuppressWarnings("unchecked") + public static final PropertyEditor<?> createNumericEditor( Item property ) { + + return new AbstractPropertyEditor<Number, NumericField>(property, new NumericField( (Class<? extends Number>) property.getType())) { + + private Class<? extends Number> sourceClass = (Class<? extends Number>) property.getType(); //Double.class; + + { enableAutoSelectAll(getEditor()); } + + @Override protected ObservableValue<Number> getObservableValue() { + return getEditor().valueProperty(); + } + + @Override public Number getValue() { + try { + return sourceClass.getConstructor(String.class).newInstance(getEditor().getText()); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + e.printStackTrace(); + return null; + } + } + + @Override public void setValue(Number value) { + sourceClass = (Class<? extends Number>) value.getClass(); + getEditor().setText(value.toString()); + } + + }; + } + + public static final PropertyEditor<?> createCheckEditor( Item property ) { + + return new AbstractPropertyEditor<Boolean, CheckBox>(property, new CheckBox()) { + + @Override protected BooleanProperty getObservableValue() { + return getEditor().selectedProperty(); + } + + @Override public void setValue(Boolean value) { + getEditor().setSelected((Boolean)value); + } + }; + + } + + public static final <T> PropertyEditor<?> createChoiceEditor( Item property, final Collection<T> choices ) { + + return new AbstractPropertyEditor<T, ComboBox<T>>(property, new ComboBox<T>()) { + + { getEditor().setItems(FXCollections.observableArrayList(choices)); } + + @Override protected ObservableValue<T> getObservableValue() { + return getEditor().getSelectionModel().selectedItemProperty(); + } + + @Override public void setValue(T value) { + getEditor().getSelectionModel().select(value); + } + }; + } + + public static final PropertyEditor<?> createColorEditor( Item property ) { + return new AbstractPropertyEditor<Color, ColorPicker>(property, new ColorPicker()) { + + @Override protected ObservableValue<Color> getObservableValue() { + return getEditor().valueProperty(); + } + + @Override public void setValue(Color value) { + getEditor().setValue((Color) value); + } + }; + } + + + public static final PropertyEditor<?> createDateEditor( Item property ) { + return new AbstractPropertyEditor<LocalDate, DatePicker>(property, new DatePicker()) { + + //TODO: Provide date picker customization support + + @Override protected ObservableValue<LocalDate> getObservableValue() { + return getEditor().valueProperty(); + } + + @Override public void setValue(LocalDate value) { + getEditor().setValue((LocalDate) value); + } + }; + } + + public static final PropertyEditor<?> createFontEditor( Item property ) { + + return new AbstractPropertyEditor<Font, AbstractObjectField<Font>>(property, new AbstractObjectField<Font>() { + @Override protected Class<Font> getType() { + return Font.class; + } + + @Override protected String objectToString(Font font) { + return font == null? "": String.format("%s, %.1f", font.getName(), font.getSize()); //$NON-NLS-1$ //$NON-NLS-2$ + } + + @Override protected Font edit(Font font) { + FontSelectorDialog dlg = new FontSelectorDialog(font); + Optional<Font> optionalFont = dlg.showAndWait(); + return optionalFont.get(); + } + }) { + + @Override protected ObservableValue<Font> getObservableValue() { + return getEditor().getObjectProperty(); + } + + @Override public void setValue(Font value) { + getEditor().getObjectProperty().set(value); + } + }; + + } + + /** + * Static method used to create an instance of the custom editor returned + * via a call to {@link Item#getPropertyEditorClass() } + * + * The class returned must declare a constructor that takes a single + * parameter of type PropertySheet.Item into which the parameter supplied + * to this method will be passed. + * + * @param property The {@link Item} that this editor will be + * associated with. + * @return The {@link PropertyEditor} wrapped in an {@link Optional} + */ + public static final Optional<PropertyEditor<?>> createCustomEditor(final Item property ) { + return property.getPropertyEditorClass().map(cls -> { + try { + Constructor<?> cn = cls.getConstructor(PropertySheet.Item.class); + if (cn != null) { + return (PropertyEditor<?>) cn.newInstance(property); + } + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + ex.printStackTrace(); + } + return null; + }); + } + + private static void enableAutoSelectAll(final TextInputControl control) { + control.focusedProperty().addListener((ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) -> { + if (newValue) { + Platform.runLater(() -> { + control.selectAll(); + }); + } + }); + } + +} diff --git a/src/org/controlsfx/property/editor/NumericField.java b/src/org/controlsfx/property/editor/NumericField.java new file mode 100644 index 0000000000000000000000000000000000000000..c90f4131fb47f53154e1dd9c77fdad1b5d3f34e7 --- /dev/null +++ b/src/org/controlsfx/property/editor/NumericField.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2015 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property.editor; + +import java.math.BigInteger; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.binding.NumberExpression; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.IndexRange; +import javafx.scene.control.TextField; + +/* + * TODO replace this with proper API when it becomes available: + * https://javafx-jira.kenai.com/browse/RT-30881 + */ +class NumericField extends TextField { + + private final NumericValidator<? extends Number> value ; + + public NumericField( Class<? extends Number> cls ) { + + if ( cls == byte.class || cls == Byte.class || cls == short.class || cls == Short.class || + cls == int.class || cls == Integer.class || cls == long.class || cls == Long.class || + cls == BigInteger.class) { + value = new LongValidator(this); + } else { + value = new DoubleValidator(this); + } + + textProperty().addListener(new InvalidationListener() { + @Override public void invalidated(Observable arg0) { + value.setValue(value.toNumber(getText())); + } + }); + + } + + public final ObservableValue<Number> valueProperty() { + return value; + } + + @Override public void replaceText(int start, int end, String text) { + if (replaceValid(start, end, text)) { + super.replaceText(start, end, text); + } + } + + @Override public void replaceSelection(String text) { + IndexRange range = getSelection(); + if (replaceValid(range.getStart(), range.getEnd(), text)) { + super.replaceSelection(text); + } + } + + private Boolean replaceValid(int start, int end, String fragment) { + try { + String newText = getText().substring(0, start) + fragment + getText().substring(end); + if (newText.isEmpty()) return true; + value.toNumber(newText); + return true; + } catch( Throwable ex ) { + return false; + } + } + + + private static abstract interface NumericValidator<T extends Number> extends NumberExpression { + void setValue(Number num); + T toNumber(String s); + + } + + static class DoubleValidator extends SimpleDoubleProperty implements NumericValidator<Double>{ + + private NumericField field; + + public DoubleValidator(NumericField field) { + super(field, "value", 0.0); //$NON-NLS-1$ + this.field = field; + } + + @Override protected void invalidated() { + field.setText(Double.toString(get())); + } + + @Override + public Double toNumber(String s) { + if ( s == null || s.trim().isEmpty() ) return 0d; + String d = s.trim(); + if ( d.endsWith("f") || d.endsWith("d") || d.endsWith("F") || d.endsWith("D") ) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + throw new NumberFormatException("There should be no alpha symbols"); //$NON-NLS-1$ + } + return new Double(d); + }; + + } + + + static class LongValidator extends SimpleLongProperty implements NumericValidator<Long>{ + + private NumericField field; + + public LongValidator(NumericField field) { + super(field, "value", 0l); //$NON-NLS-1$ + this.field = field; + } + + @Override protected void invalidated() { + field.setText(Long.toString(get())); + } + + @Override + public Long toNumber(String s) { + if ( s == null || s.trim().isEmpty() ) return 0l; + String d = s.trim(); + return new Long(d); + }; + + } + + +} \ No newline at end of file diff --git a/src/org/controlsfx/property/editor/PropertyEditor.java b/src/org/controlsfx/property/editor/PropertyEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..58dbf36c14933594a9fe4ca78a391a4aa62ce332 --- /dev/null +++ b/src/org/controlsfx/property/editor/PropertyEditor.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.property.editor; + +import org.controlsfx.control.PropertySheet; + +import javafx.scene.Node; + +/** + * The base interface for all editors used by the {@link PropertySheet} control. + * + * @param <T> The type of the property that the PropertyEditor is responsible + * for editing. + */ +public interface PropertyEditor<T> { + + /** + * Returns the editor responsible for editing this property. + */ + public Node getEditor(); + + /** + * Returns the current value in the editor - this may not be the value of + * the property itself! + */ + public T getValue(); + + /** + * Sets the value to display in the editor - this may not be the value of + * the property itself - and the property value will not change! + */ + public void setValue(T value); +} diff --git a/src/org/controlsfx/property/editor/package-info.java b/src/org/controlsfx/property/editor/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..67fd7dfe2d1c28b114fea46d707057786d3702cc --- /dev/null +++ b/src/org/controlsfx/property/editor/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing a number of useful editor classes related to the + * {@link org.controlsfx.control.PropertySheet} control. + */ +package org.controlsfx.property.editor; \ No newline at end of file diff --git a/src/org/controlsfx/property/package-info.java b/src/org/controlsfx/property/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..366b5b56a40c39e8c55fee069609fa8684acd6f5 --- /dev/null +++ b/src/org/controlsfx/property/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing a number of useful classes related to the + * {@link org.controlsfx.control.PropertySheet} control. + */ +package org.controlsfx.property; \ No newline at end of file diff --git a/src/org/controlsfx/tools/Borders.java b/src/org/controlsfx/tools/Borders.java new file mode 100644 index 0000000000000000000000000000000000000000..542c2f4dea56aad72c4b029e5465df0736710eff --- /dev/null +++ b/src/org/controlsfx/tools/Borders.java @@ -0,0 +1,807 @@ +/** + * Copyright (c) 2013, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.tools; + +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A utility class that allows you to wrap JavaFX {@link Node Nodes} with a border, + * in a way somewhat analogous to the Swing {@link BorderFactory} (although with + * less options as a lot of what the Swing BorderFactory offers resulted in + * ugly borders!). + * + * <p>The Borders class provides a fluent API for specifying the properties of + * each border. It is possible to create multiple borders around a Node simply + * by continuing to call additional methods before you call the final + * {@link Borders#build()} method. To use the Borders class, you simply call + * {@link Borders#wrap(Node)}, passing in the Node you wish to wrap the border(s) + * around. + * + * <h3>Examples</h3> + * <p>Firstly, lets wrap a JavaFX Button node with a simple line border that looks + * like the following: + * + * <br> + * <center><img src="borders-lineBorder.png" alt="Screenshot of Borders.LineBorders"></center> + * + * <p>Here's the code:</p> + * + * <pre> + * {@code + * Button button = new Button("Hello World!"); + * Node wrappedButton = Borders.wrap(button).lineBorder().buildAll(); + * }</pre> + * + * <p>Easy, isn't it!? You can make the border look a little nicer by replacing + * the line border with an {@link EtchedBorders etched border}. An etched border + * has a subtle inner (or outer) line that makes the border stand out a bit more, + * like this: + * + * <br> + * <center><img src="borders-etchedBorder.png" alt="Screenshot of Borders.EtchedBorders"></center> + * + * <p>Now that's one good looking border! Here's the code:</p> + * + * <pre> + * {@code + * Button button = new Button("Hello World!"); + * Node wrappedButton = Borders.wrap(button).etchedBorder().buildAll(); + * }</pre> + * + * <p>In some circumstances you want to have multiple borders. For example, + * you might two line borders. That's easy: + * + * <br> + * <center><img src="borders-twoLines.png" alt="Screenshot of two Borders.LineBorders"></center> + * + * <pre> + * {@code + * Node wrappedButton = Borders.wrap(button) + * .lineBorder().color(Color.RED).build() + * .lineBorder().color(Color.GREEN).build() + * .build(); + * }</pre> + * + * <p>You simply chain the borders together, going from inside to outside!</p> + * + * <p>Because of all the configuration options it isn't possible to list all the + * functionality of all the border types, so refer to the rest of the javadocs + * for inspiration.</p> + */ +public final class Borders { + + /************************************************************************** + * + * Static fields + * + **************************************************************************/ + + private static final Color DEFAULT_BORDER_COLOR = Color.DARKGRAY; + + + + /************************************************************************** + * + * Internal fields + * + **************************************************************************/ + + private final Node node; + private final List<Border> borders; + + + + /************************************************************************** + * + * Fluent API entry method(s) + * + **************************************************************************/ + + public static Borders wrap(Node n) { + return new Borders(n); + } + + + + /************************************************************************** + * + * Private Constructor + * + **************************************************************************/ + + private Borders(Node n) { + this.node = n; + this.borders = new ArrayList<>(); + } + + + + /************************************************************************** + * + * Fluent API + * + **************************************************************************/ + + /** + * Often times it is useful to have a bit of whitespace around a Node, to + * separate it from what it is next to. Call this method to begin building + * a border that will wrap the node with a given amount of whitespace + * (which can vary between the top, right, bottom, and left sides). + */ + public EmptyBorders emptyBorder() { + return new EmptyBorders(this); + } + + /** + * The etched border look is essentially equivalent to the {@link #lineBorder()} + * look, except rather than one line, there are two. What is commonly done in + * this circumstance is that one of the lines is a very light colour (commonly + * white), which gives a nice etched look. Refer to the API in {@link EtchedBorders} + * for more information. + */ + public EtchedBorders etchedBorder() { + return new EtchedBorders(this); + } + + /** + * Creates a nice, simple border around the node. Note that there are many + * configuration options in {@link LineBorders}, so explore it carefully. + */ + public LineBorders lineBorder() { + return new LineBorders(this); + } + + /** + * Allows for developers to develop custom {@link Border} implementations, + * and to wrap them around a Node. Note that of course this is mostly + * redundant (as you could just call {@link Border#wrap(Node)} directly). + * The only benefit is if you're creating a compound border consisting of + * multiple borders, and you want your custom border included as part of + * this. + */ + public Borders addBorder(Border border) { + borders.add(border); + return this; + } + + /** + * Returns the original node wrapped in zero or more borders, as specified + * using the fluent API. + */ + public Node build() { + // we iterate through the borders list in reverse order + Node bundle = node; + for (int i = borders.size() - 1; i >= 0; i--) { + Border border = borders.get(i); + bundle = border.wrap(bundle); + } + return bundle; + } + + + + /************************************************************************** + * + * Support classes + * + **************************************************************************/ + + /** + * A fluent API that is only indirectly instantiable via the {@link Borders} + * fluent API, and which allows for an {@link Borders#emptyBorder() empty border} + * to be wrapped around a given Node. + */ + public class EmptyBorders { + private final Borders parent; + + private double top; + private double right; + private double bottom; + private double left; + + // private on purpose - this class is not directly instantiable. + private EmptyBorders(Borders parent) { + this.parent = parent; + } + + /** + * Specifies that the wrapped Node should have the given padding around + * all four sides of itself. + */ + public EmptyBorders padding(double padding) { + return padding(padding, padding, padding, padding); + } + + /** + * Specifies that the wrapped Node should be wrapped with the given + * padding for each of its four sides, going in the order top, right, + * bottom, and finally left. + */ + public EmptyBorders padding(double top, double right, double bottom, double left) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + return this; + } + + /** + * Builds the {@link Border} and {@link Borders#addBorder(Border) adds it} + * to the list of borders to wrap around the given Node (which will be + * constructed and returned when {@link Borders#build()} is called. + */ + public Borders build() { + parent.addBorder(new StrokeBorder(null, buildStroke())); + return parent; + } + + /** + * A convenience method, this is equivalent to calling + * {@link #build()} followed by {@link Borders#build()}. In other words, + * calling this will return the original Node wrapped in all its borders + * specified. + */ + public Node buildAll() { + build(); + return parent.build(); + } + + private BorderStroke buildStroke() { + return new BorderStroke( + null, + BorderStrokeStyle.NONE, + null, + new BorderWidths(top, right, bottom, left), + Insets.EMPTY); + } + } + + /** + * A fluent API that is only indirectly instantiable via the {@link Borders} + * fluent API, and which allows for an {@link Borders#etchedBorder() etched border} + * to be wrapped around a given Node. + */ + public class EtchedBorders { + private final Borders parent; + + private String title; + private boolean raised = false; + + private double outerTopPadding = 10; + private double outerRightPadding = 10; + private double outerBottomPadding = 10; + private double outerLeftPadding = 10; + + private double innerTopPadding = 15; + private double innerRightPadding = 15; + private double innerBottomPadding = 15; + private double innerLeftPadding = 15; + + private double topLeftRadius = 0; + private double topRightRadius = 0; + private double bottomRightRadius = 0; + private double bottomLeftRadius = 0; + + private Color highlightColor = DEFAULT_BORDER_COLOR; + private Color shadowColor = Color.WHITE; + + // private on purpose - this class is not directly instantiable. + private EtchedBorders(Borders parent) { + this.parent = parent; + } + + /** + * Specifies the highlight colour to use in the etched border. + */ + public EtchedBorders highlight(Color highlight) { + this.highlightColor = highlight; + return this; + } + + /** + * Specifies the shadow colour to use in the etched border. + */ + public EtchedBorders shadow(Color shadow) { + this.shadowColor = shadow; + return this; + } + + /** + * Specifies the order in which the highlight and shadow colours are + * placed. A raised etched border has the shadow colour on the outside + * of the border, whereas a non-raised (or lowered) etched border has + * the shadow colour on the inside of the border. + */ + public EtchedBorders raised() { + raised = true; + return this; + } + + /** + * If desired, this specifies the title text to show in this border. + */ + public EtchedBorders title(String title) { + this.title = title; + return this; + } + + /** + * Specifies the outer padding of the four lines of this border. + */ + public EtchedBorders outerPadding(double padding) { + return outerPadding(padding, padding, padding, padding); + } + + /** + * Specifies that the line wrapping the node should have outer padding + * as specified, with each padding being independently configured, going + * in the order top, right, bottom, and left. + */ + public EtchedBorders outerPadding(double topPadding, double rightPadding, double bottomPadding, double leftPadding) { + this.outerTopPadding = topPadding; + this.outerRightPadding = rightPadding; + this.outerBottomPadding = bottomPadding; + this.outerLeftPadding = leftPadding; + + return this; + } + + /** + * Specifies the inner padding of the four lines of this border. + */ + public EtchedBorders innerPadding(double padding) { + return innerPadding(padding, padding, padding, padding); + } + + /** + * Specifies that the line wrapping the node should have inner padding + * as specified, with each padding being independently configured, going + * in the order top, right, bottom, and left. + */ + public EtchedBorders innerPadding(double topPadding, double rightPadding, double bottomPadding, double leftPadding) { + this.innerTopPadding = topPadding; + this.innerRightPadding = rightPadding; + this.innerBottomPadding = bottomPadding; + this.innerLeftPadding = leftPadding; + + return this; + } + + /** + * Specifies the radius of the four corners of the lines of this border. + */ + public EtchedBorders radius(double radius) { + return radius(radius, radius, radius, radius); + } + + /** + * Specifies that the etched line wrapping the node should have corner radii + * as specified, with each radius being independently configured, going + * in the order top-left, top-right, bottom-right, and finally bottom-left. + */ + public EtchedBorders radius(double topLeft, double topRight, double bottomRight, double bottomLeft) { + this.topLeftRadius = topLeft; + this.topRightRadius = topRight; + this.bottomRightRadius = bottomRight; + this.bottomLeftRadius = bottomLeft; + return this; + } + + /** + * Builds the {@link Border} and {@link Borders#addBorder(Border) adds it} + * to the list of borders to wrap around the given Node (which will be + * constructed and returned when {@link Borders#build()} is called. + */ + public Borders build() { + Color inner = raised ? shadowColor : highlightColor; + Color outer = raised ? highlightColor : shadowColor; + BorderStroke innerStroke = new BorderStroke( + inner, + BorderStrokeStyle.SOLID, + new CornerRadii(topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, false), + new BorderWidths(1)); + BorderStroke outerStroke = new BorderStroke( + outer, + BorderStrokeStyle.SOLID, + new CornerRadii(topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, false), + new BorderWidths(1), + new Insets(1)); + + BorderStroke outerPadding = new EmptyBorders(parent) + .padding(outerTopPadding, outerRightPadding, outerBottomPadding, outerLeftPadding) + .buildStroke(); + + BorderStroke innerPadding = new EmptyBorders(parent) + .padding(innerTopPadding, innerRightPadding, innerBottomPadding, innerLeftPadding) + .buildStroke(); + + parent.addBorder(new StrokeBorder(null, outerPadding)); + parent.addBorder(new StrokeBorder(title, innerStroke, outerStroke)); + parent.addBorder(new StrokeBorder(null, innerPadding)); + + return parent; + } + + /** + * A convenience method, this is equivalent to calling + * {@link #build()} followed by {@link Borders#build()}. In other words, + * calling this will return the original Node wrapped in all its borders + * specified. + */ + public Node buildAll() { + build(); + return parent.build(); + } + } + + /** + * A fluent API that is only indirectly instantiable via the {@link Borders} + * fluent API, and which allows for a {@link Borders#lineBorder() line border} + * to be wrapped around a given Node. + */ + public class LineBorders { + private final Borders parent; + + private String title; + + private BorderStrokeStyle strokeStyle = BorderStrokeStyle.SOLID; + + private Color topColor = DEFAULT_BORDER_COLOR; + private Color rightColor = DEFAULT_BORDER_COLOR; + private Color bottomColor = DEFAULT_BORDER_COLOR; + private Color leftColor = DEFAULT_BORDER_COLOR; + + private double outerTopPadding = 10; + private double outerRightPadding = 10; + private double outerBottomPadding = 10; + private double outerLeftPadding = 10; + + private double innerTopPadding = 15; + private double innerRightPadding = 15; + private double innerBottomPadding = 15; + private double innerLeftPadding = 15; + + private double topThickness = 1; + private double rightThickness = 1; + private double bottomThickness = 1; + private double leftThickness = 1; + + private double topLeftRadius = 0; + private double topRightRadius = 0; + private double bottomRightRadius = 0; + private double bottomLeftRadius = 0; + + // private on purpose - this class is not directly instantiable. + private LineBorders(Borders parent) { + this.parent = parent; + } + + /** + * Specifies the colour to use for all four sides of this border. + */ + public LineBorders color(Color color) { + return color(color, color, color, color); + } + + /** + * Specifies that the wrapped Node should be wrapped with the given + * colours for each of its four sides, going in the order top, right, + * bottom, and finally left. + */ + public LineBorders color(Color topColor, Color rightColor, Color bottomColor, Color leftColor) { + this.topColor = topColor; + this.rightColor = rightColor; + this.bottomColor = bottomColor; + this.leftColor = leftColor; + return this; + } + + /** + * Specifies which {@link BorderStrokeStyle} to use for this line border. + * By default this is {@link BorderStrokeStyle#SOLID}, but you can use + * any other style (such as {@link BorderStrokeStyle#DASHED}, + * {@link BorderStrokeStyle#DOTTED}, or a custom style built using + * {@link BorderStrokeStyle#BorderStrokeStyle(javafx.scene.shape.StrokeType, javafx.scene.shape.StrokeLineJoin, javafx.scene.shape.StrokeLineCap, double, double, List)}. + */ + public LineBorders strokeStyle(BorderStrokeStyle strokeStyle) { + this.strokeStyle = strokeStyle; + return this; + } + + /** + * Specifies the inner padding of the four lines of this border. + */ + public LineBorders outerPadding(double padding) { + return outerPadding(padding, padding, padding, padding); + } + + /** + * Specifies that the line wrapping the node should have outer padding + * as specified, with each padding being independently configured, going + * in the order top, right, bottom, and left. + */ + public LineBorders outerPadding(double topPadding, double rightPadding, double bottomPadding, double leftPadding) { + this.outerTopPadding = topPadding; + this.outerRightPadding = rightPadding; + this.outerBottomPadding = bottomPadding; + this.outerLeftPadding = leftPadding; + + return this; + } + + /** + * Specifies the outer padding of the four lines of this border. + */ + public LineBorders innerPadding(double padding) { + return innerPadding(padding, padding, padding, padding); + } + + /** + * Specifies that the line wrapping the node should have inner padding + * as specified, with each padding being independently configured, going + * in the order top, right, bottom, and left. + */ + public LineBorders innerPadding(double topPadding, double rightPadding, double bottomPadding, double leftPadding) { + this.innerTopPadding = topPadding; + this.innerRightPadding = rightPadding; + this.innerBottomPadding = bottomPadding; + this.innerLeftPadding = leftPadding; + + return this; + } + + /** + * Specifies the thickness of the line to use on all four sides of this + * border. + */ + public LineBorders thickness(double thickness) { + return thickness(thickness, thickness, thickness, thickness); + } + + /** + * Specifies that the wrapped Node should be wrapped with the given + * line thickness for each of its four sides, going in the order top, right, + * bottom, and finally left. + */ + public LineBorders thickness(double topThickness, double rightThickness, double bottomThickness, double leftThickness) { + this.topThickness = topThickness; + this.rightThickness = rightThickness; + this.bottomThickness = bottomThickness; + this.leftThickness = leftThickness; + return this; + } + + /** + * Specifies the radius of the four corners of the line of this border. + */ + public LineBorders radius(double radius) { + return radius(radius, radius, radius, radius); + } + + /** + * Specifies that the line wrapping the node should have corner radii + * as specified, with each radius being independently configured, going + * in the order top-left, top-right, bottom-right, and finally bottom-left. + */ + public LineBorders radius(double topLeft, double topRight, double bottomRight, double bottomLeft) { + this.topLeftRadius = topLeft; + this.topRightRadius = topRight; + this.bottomRightRadius = bottomRight; + this.bottomLeftRadius = bottomLeft; + return this; + } + + /** + * If desired, this specifies the title text to show in this border. + */ + public LineBorders title(String title) { + this.title = title; + return this; + } + + /** + * Builds the {@link Border} and {@link Borders#addBorder(Border) adds it} + * to the list of borders to wrap around the given Node (which will be + * constructed and returned when {@link Borders#build()} is called. + */ + public Borders build() { + BorderStroke borderStroke = new BorderStroke( + topColor, rightColor, bottomColor, leftColor, + strokeStyle, strokeStyle, strokeStyle, strokeStyle, + new CornerRadii(topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius, false), + new BorderWidths(topThickness, rightThickness, bottomThickness, leftThickness), + null); + + BorderStroke outerPadding = new EmptyBorders(parent) + .padding(outerTopPadding, outerRightPadding, outerBottomPadding, outerLeftPadding) + .buildStroke(); + + BorderStroke innerPadding = new EmptyBorders(parent) + .padding(innerTopPadding, innerRightPadding, innerBottomPadding, innerLeftPadding) + .buildStroke(); + + parent.addBorder(new StrokeBorder(null, outerPadding)); + parent.addBorder(new StrokeBorder(title, borderStroke)); + parent.addBorder(new StrokeBorder(null, innerPadding)); + + return parent; + } + + /** + * A convenience method, this is equivalent to calling + * {@link #build()} followed by {@link Borders#build()}. In other words, + * calling this will return the original Node wrapped in all its borders + * specified. + */ + public Node buildAll() { + build(); + return parent.build(); + } + } + + + + /************************************************************************** + * + * Support interfaces + * + **************************************************************************/ + + /** + * The public interface used by the {@link Borders} API to wrap nodes with + * zero or more Border implementations. ControlsFX ships with a few + * Border implementations (current {@link EmptyBorders}, {@link LineBorders}, + * and {@link EtchedBorders}). As noted in {@link Borders#addBorder(Border)}, + * this interface is relatively pointless, unless you plan to wrap a node + * with multiple borders and you want to use a custom {@link Border} + * implementation for at least one border. In this case, you can simply + * call {@link Borders#addBorder(Border)} with your custom border, when + * appropriate. + */ + @FunctionalInterface + public static interface Border { + + /** + * Given a {@link Node}, this method should return a Node that contains + * the original Node and also has wrapped it with an appropriate border. + */ + public Node wrap(Node n); + } + + + + /************************************************************************** + * + * Private support classes + * + **************************************************************************/ + + // --- Border implementations + private static class StrokeBorder implements Border { + private static final int TITLE_PADDING = 3; + private static final double GAP_PADDING = TITLE_PADDING * 2 - 1; + + private final String title; + private final BorderStroke[] borderStrokes; + + public StrokeBorder(String title, BorderStroke... borderStrokes) { + this.title = title; + this.borderStrokes = borderStrokes; + } + + @Override public Node wrap(final Node n) { + StackPane pane = new StackPane() { + Label titleLabel; + + { + // add in the node we are wrapping + getChildren().add(n); + + + // if the title string is set, then also add in the title label + if (title != null) { + titleLabel = new Label(title); + + // give the text a bit of space on the left... + titleLabel.setPadding(new Insets(0, 0, 0, TITLE_PADDING)); + getChildren().add(titleLabel); + } + } + + @Override protected void layoutChildren() { + super.layoutChildren(); + + if (titleLabel != null) { + // layout the title label + final double labelHeight = titleLabel.prefHeight(-1); + final double labelWidth = titleLabel.prefWidth(labelHeight) + TITLE_PADDING; + titleLabel.resize(labelWidth, labelHeight); + titleLabel.relocate(TITLE_PADDING * 2, -labelHeight / 2.0 - 1); + + List<BorderStroke> newBorderStrokes = new ArrayList<>(2); + + // create a line gap for the title label + for (BorderStroke bs : borderStrokes) { + List<Double> dashList = new ArrayList<>(); + + // Create a dash list for the line gap or add it at the beginning of an existing dash list. This gap should be wide enough for the title label. + if (bs.getTopStyle().getDashArray().isEmpty()) + dashList.addAll(Arrays.asList(GAP_PADDING, labelWidth, Double.MAX_VALUE)); + else { // dash pattern exists + // insert gap in existing dash pattern and multiply original pattern so that gap does not show more then once + double origDashWidth = bs.getTopStyle().getDashArray().stream().mapToDouble(d -> d).sum(); + + if (origDashWidth > GAP_PADDING) { + dashList.add(GAP_PADDING); + dashList.add(labelWidth); + } else { // need to insert dash pattern before the gap + int no = (int) (GAP_PADDING / origDashWidth); + + for (int i = 0; i < no; i++) + dashList.addAll(bs.getTopStyle().getDashArray()); + + if ((dashList.size() & 1) == 0) // if size is even number, add one more element because gap must be at odd index to be transparent + dashList.add(0d); + + dashList.add(labelWidth + GAP_PADDING - no * origDashWidth); + } + + for (int i = 0; i < (getWidth() - labelWidth - origDashWidth) / origDashWidth; i++) + dashList.addAll(bs.getTopStyle().getDashArray()); + } + + // create new border stroke style for the top border line with new dash list + BorderStrokeStyle topStrokeStyle = new BorderStrokeStyle( + bs.getTopStyle().getType(), bs.getTopStyle().getLineJoin(), bs.getTopStyle().getLineCap(), + bs.getTopStyle().getMiterLimit(), bs.getTopStyle().getDashOffset(), dashList); + + // change existing border stroke to utilize new top border line stroke style + newBorderStrokes.add(new BorderStroke( + bs.getTopStroke(), bs.getRightStroke(), bs.getBottomStroke(), bs.getLeftStroke(), + topStrokeStyle, bs.getRightStyle(), bs.getBottomStyle(), bs.getLeftStyle(), + bs.getRadii(), bs.getWidths(), null)); + } + + setBorder(new javafx.scene.layout.Border(newBorderStrokes.toArray(new BorderStroke[newBorderStrokes.size()]))); + } + } + }; + + pane.setBorder(new javafx.scene.layout.Border(borderStrokes)); + return pane; + } + } +} diff --git a/src/org/controlsfx/tools/Duplicatable.java b/src/org/controlsfx/tools/Duplicatable.java new file mode 100644 index 0000000000000000000000000000000000000000..1fd50fd4456cc978ba2e5c78c9b84583fc0ecab5 --- /dev/null +++ b/src/org/controlsfx/tools/Duplicatable.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.tools; + +import javafx.scene.Node; + +/** + * An interface used in ControlsFX to represent something that can be duplicated, + * as in the JavaFX scenegraph it is not possible to insert the same + * {@link Node} in multiple locations at the same time. Therefore, to work + * around this the node may implement this interface to duplicate itself. + * + * @param <T> The node type + */ +@FunctionalInterface +public interface Duplicatable<T> { + T duplicate(); +} diff --git a/src/org/controlsfx/tools/Platform.java b/src/org/controlsfx/tools/Platform.java new file mode 100644 index 0000000000000000000000000000000000000000..3a0eae765cfd0fe2580ed27c6e713fe91702b30f --- /dev/null +++ b/src/org/controlsfx/tools/Platform.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.tools; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Represents operating system with appropriate properties + * + */ +public enum Platform { + + WINDOWS("windows"), //$NON-NLS-1$ + OSX("mac"), //$NON-NLS-1$ + UNIX("unix"), //$NON-NLS-1$ + UNKNOWN(""); //$NON-NLS-1$ + + private static Platform current = getCurrentPlatform(); + + private String platformId; + + Platform( String platformId ) { + this.platformId = platformId; + } + + /** + * Returns platform id. Usually used to specify platform dependent styles + * @return platform id + */ + public String getPlatformId() { + return platformId; + } + + /** + * @return the current OS. + */ + public static Platform getCurrent() { + return current; + } + + private static Platform getCurrentPlatform() { + String osName = System.getProperty("os.name"); + if ( osName.startsWith("Windows") ) return WINDOWS; + if ( osName.startsWith("Mac") ) return OSX; + if ( osName.startsWith("SunOS") ) return UNIX; + if ( osName.startsWith("Linux") ) { + String javafxPlatform = AccessController.doPrivileged(new PrivilegedAction<String>() { + @Override + public String run() { + return System.getProperty("javafx.platform"); + } + }); + if (! ( "android".equals(javafxPlatform) || "Dalvik".equals(System.getProperty("java.vm.name")) ) ) // if not Android + return UNIX; + } + return UNKNOWN; + } + +} diff --git a/src/org/controlsfx/tools/SVGLoader.java b/src/org/controlsfx/tools/SVGLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..c25ecb106dbf8121f1e8f9d32281ce89d7338f25 --- /dev/null +++ b/src/org/controlsfx/tools/SVGLoader.java @@ -0,0 +1,227 @@ +/** + * Copyright (c) 2013, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.tools; + +import java.net.URL; + +import javafx.beans.value.ChangeListener; +import javafx.concurrent.Worker.State; +import javafx.geometry.Rectangle2D; +import javafx.scene.Scene; +import javafx.scene.SnapshotParameters; +import javafx.scene.SnapshotResult; +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.scene.paint.Color; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import javafx.stage.Stage; +import javafx.util.Callback; + +import com.sun.javafx.webkit.Accessor; +import com.sun.webkit.WebPage; + +/** + * Convenience class that will attempt to load a given URL as an .svg file. + */ +class SVGLoader { + + private SVGLoader() { + // no-op + } + + /** + * This method will attempt to load the given svgImage URL into an ImageView + * node that will be provided asynchronously via the provided + * {@link Callback}, and it will be sized to the given prefWidth / prefHeight. + * + * <p>Note that it is valid to pass in -1 to prefWidth and / or prefHeight as + * an indicator to the SVG loader. If both values are -1, the default width + * of the SVG will be used. If one of the values is -1, then the SVG will + * be sized to ensure that it remains proportional. + * + * @param svgImage The image to load. + * @param prefWidth The preferred width of the image when loaded, or -1 if + * there is no preferred width. + * @param prefHeight The preferred height of the image when loaded, or -1 if + * there is no preferred height. + * @param callback The {@link Callback} that will be called when the SVG + * image is loaded, where the {@link ImageView} containing the rendered + * image will be available. + */ + public static void loadSVGImage(final URL svgImage, + final double prefWidth, + final double prefHeight, + final Callback<ImageView, Void> callback) { + loadSVGImage(svgImage, prefWidth, prefHeight, callback, null); + } + + /** + * This method will attempt to load the given svgImage URL into the provided + * {@link WritableImage}, with the SVG scaled to fit the size of the + * WritableImage. + * + * @param svgImage The image to load. + * @param outputImage The location to write the loaded image once it has + * been rendered (it will not happen synchronously). + * @throws NullPointerException The outputImage argument must be non-null. + */ + public static void loadSVGImage(final URL svgImage, + final WritableImage outputImage) { + if (outputImage == null) { + throw new NullPointerException("outputImage can not be null"); //$NON-NLS-1$ + } + final double w = outputImage.getWidth(); + final double h = outputImage.getHeight(); + loadSVGImage(svgImage, w, h, null, outputImage); + } + + public static void loadSVGImage(final URL svgImage, + final double prefWidth, + final double prefHeight, + final Callback<ImageView, Void> callback, + final WritableImage outputImage) { + final WebView view = new WebView(); + final WebEngine eng = view.getEngine(); + + // using non-public API to ensure background transparency + final WebPage webPage = Accessor.getPageFor(eng); + webPage.setBackgroundColor(webPage.getMainFrame(), 0xffffff00); + webPage.setOpaque(webPage.getMainFrame(), false); + // end of non-public API + + // temporary scene / stage + final Scene scene = new Scene(view); + final Stage stage = new Stage(); + stage.setScene(scene); + stage.setWidth(0); + stage.setHeight(0); + stage.setOpacity(0); + stage.show(); + +// String svgString = readFile(svgImage); + + String content = + "<html>" + //$NON-NLS-1$ + "<body style=\"margin-top: 0px; margin-bottom: 30px; margin-left: 0px; margin-right: 0px; padding: 0;\">" + //$NON-NLS-1$ +// "<div style=\"width: " + prefWidth + "; height: " + prefHeight + ";\">" + + "<img id=\"svgImage\" style=\"display: block;float: top;\" width=\"" + prefWidth + "\" height=\"" + prefHeight + "\" src=\"" + svgImage.toExternalForm() + "\" />" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ +// svgString + +// "</div>" + + "</body>" + //$NON-NLS-1$ + "</head>"; //$NON-NLS-1$ + + + + eng.loadContent(content); + + eng.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() { + @Override public void changed(javafx.beans.value.ObservableValue<? extends State> o, State oldValue, State newValue) { + if (newValue == State.SUCCEEDED) { + +// HTMLImageElement svgImageElement = (HTMLImageElement) getSvgDom(eng); +// System.out.println(svgImageElement.getAttributes()); + + final double svgWidth = prefWidth >= 0 ? prefWidth : getSvgWidth(eng); + final double svgHeight = prefHeight >= 0 ? prefWidth : getSvgHeight(eng); + + SnapshotParameters params = new SnapshotParameters(); + params.setFill(Color.TRANSPARENT); + params.setViewport(new Rectangle2D(0, 0, svgWidth, svgHeight)); + + view.snapshot(new Callback<SnapshotResult, Void>() { + @Override public Void call(SnapshotResult param) { + WritableImage snapshot = param.getImage(); + ImageView image = new ImageView(snapshot); + + if (callback != null) { + callback.call(image); + } + + stage.hide(); + return null; + } + }, params, outputImage); + } + } + }); + } + +// private static String readFile(URL url) { +// try { +// FileInputStream stream = new FileInputStream(new File(url.toURI())); +// try { +// FileChannel fc = stream.getChannel(); +// MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); +// return Charset.defaultCharset().decode(bb).toString(); +// } +// finally { +// stream.close(); +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return null; +// } + + private static double getSvgWidth(WebEngine webEngine) { + Object result = getSvgDomProperty(webEngine, "offsetWidth"); //$NON-NLS-1$ + if (result instanceof Integer) { + return (Integer) result; + } + return -1; + } + + private static double getSvgHeight(WebEngine webEngine) { + Object result = getSvgDomProperty(webEngine, "offsetHeight"); //$NON-NLS-1$ + if (result instanceof Integer) { + return (Integer) result; + } + return -1; + } + + private static Object getSvgDomProperty(final WebEngine webEngine, final String property) { + return webEngine.executeScript("document.getElementById('svgImage')." + property); //$NON-NLS-1$ + } + +// private static HTMLImageElement getSvgDom(WebEngine webEngine) { +// return (HTMLImageElement) webEngine.executeScript("document.getElementById('svgImage')"); +// } +// +// private static void printDocument(Document doc, OutputStream out) throws IOException, TransformerException { +// TransformerFactory tf = TransformerFactory.newInstance(); +// Transformer transformer = tf.newTransformer(); +// transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); +// transformer.setOutputProperty(OutputKeys.METHOD, "xml"); +// transformer.setOutputProperty(OutputKeys.INDENT, "yes"); +// transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); +// transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); +// +// transformer.transform(new DOMSource(doc), +// new StreamResult(new OutputStreamWriter(out, "UTF-8"))); +// } +} diff --git a/src/org/controlsfx/tools/Utils.java b/src/org/controlsfx/tools/Utils.java new file mode 100644 index 0000000000000000000000000000000000000000..a7b236d82be055d3bc1e8ad2f3a30f05d4677414 --- /dev/null +++ b/src/org/controlsfx/tools/Utils.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.tools; + +import java.util.Iterator; + +import javafx.scene.Node; +import javafx.stage.PopupWindow; +import javafx.stage.Window; + +public class Utils { + + /** + * Will return a {@link Window} from an object if any can be found. {@code null} + * value can be given, the program will then try to find the focused window + * among those available. + * + * @param owner the object whose window is to be found. + * @return the window of the given object. + */ + public static Window getWindow(Object owner) throws IllegalArgumentException { + if (owner == null) { + Window window = null; + // lets just get the focused stage and show the dialog in there + @SuppressWarnings("deprecation") + Iterator<Window> windows = Window.impl_getWindows(); + while (windows.hasNext()) { + window = windows.next(); + if (window.isFocused() && !(window instanceof PopupWindow)) { + break; + } + } + return window; + } else if (owner instanceof Window) { + return (Window) owner; + } else if (owner instanceof Node) { + return ((Node) owner).getScene().getWindow(); + } else { + throw new IllegalArgumentException("Unknown owner: " + owner.getClass()); //$NON-NLS-1$ + } + } + + /** + * Return a letter (just like Excel) associated with the number. When the + * number is under 26, a simple letter is returned. When the number is + * superior, concatenated letters are returned. + * + * + * For example: 0 -> A 1 -> B 26 -> AA 32 -> AG 45 -> AT + * + * + * @param number the number whose Excel Letter is to be found. + * @return a letter (like) associated with the number. + */ + public static final String getExcelLetterFromNumber(int number) { + String letter = ""; //$NON-NLS-1$ + // Repeatedly divide the number by 26 and convert the + // remainder into the appropriate letter. + while (number >= 0) { + final int remainder = number % 26; + letter = (char) (remainder + 'A') + letter; + number = number / 26 - 1; + } + + return letter; + } + + /** + * Simple utility function which clamps the given value to be strictly + * between the min and max values. + */ + public static double clamp(double min, double value, double max) { + if (value < min) return min; + if (value > max) return max; + return value; + } + + /** + * Utility function which returns either {@code less} or {@code more} + * depending on which {@code value} is closer to. If {@code value} + * is perfectly between them, then either may be returned. + */ + public static double nearest(double less, double value, double more) { + double lessDiff = value - less; + double moreDiff = more - value; + if (lessDiff < moreDiff) return less; + return more; + } +} diff --git a/src/org/controlsfx/tools/ValueExtractor.java b/src/org/controlsfx/tools/ValueExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..b64b4ed67df7226a91e70b97a4844c3d5e642535 --- /dev/null +++ b/src/org/controlsfx/tools/ValueExtractor.java @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2014 ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.tools; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Predicate; + +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.scene.Node; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ColorPicker; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Control; +import javafx.scene.control.DatePicker; +import javafx.scene.control.ListView; +import javafx.scene.control.MultipleSelectionModel; +import javafx.scene.control.RadioButton; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.Slider; +import javafx.scene.control.TableView; +import javafx.scene.control.TextInputControl; +import javafx.scene.control.TreeTableView; +import javafx.scene.control.TreeView; +import javafx.util.Callback; + +public class ValueExtractor { + + private static class ObservableValueExtractor { + + public final Predicate<Control> applicability; + public final Callback<Control, ObservableValue<?>> extraction; + + public ObservableValueExtractor( Predicate<Control> applicability, Callback<Control, ObservableValue<?>> extraction ) { + this.applicability = Objects.requireNonNull(applicability); + this.extraction = Objects.requireNonNull(extraction); + } + + } + + private static List<ObservableValueExtractor> extractors = FXCollections.observableArrayList(); + + /** + * Add "obervable value extractor" for custom controls. + * @param test applicability test + * @param extract extraction of observable value + */ + public static void addObservableValueExtractor( Predicate<Control> test, Callback<Control, ObservableValue<?>> extract ) { + extractors.add( new ObservableValueExtractor(test, extract)); + } + + static { + addObservableValueExtractor( c -> c instanceof TextInputControl, c -> ((TextInputControl)c).textProperty()); + addObservableValueExtractor( c -> c instanceof ComboBox, c -> ((ComboBox<?>)c).valueProperty()); + addObservableValueExtractor( c -> c instanceof ChoiceBox, c -> ((ChoiceBox<?>)c).valueProperty()); + addObservableValueExtractor( c -> c instanceof CheckBox, c -> ((CheckBox)c).selectedProperty()); + addObservableValueExtractor( c -> c instanceof Slider, c -> ((Slider)c).valueProperty()); + addObservableValueExtractor( c -> c instanceof ColorPicker, c -> ((ColorPicker)c).valueProperty()); + addObservableValueExtractor( c -> c instanceof DatePicker, c -> ((DatePicker)c).valueProperty()); + + addObservableValueExtractor( c -> c instanceof ListView, c -> ((ListView<?>)c).itemsProperty()); + addObservableValueExtractor( c -> c instanceof TableView, c -> ((TableView<?>)c).itemsProperty()); + + // FIXME: How to listen for TreeView changes??? + //addObservableValueExtractor( c -> c instanceof TreeView, c -> ((TreeView<?>)c).Property()); + } + + + + public static final Optional<Callback<Control, ObservableValue<?>>> getObservableValueExtractor(final Control c) { + for( ObservableValueExtractor e: extractors ) { + if ( e.applicability.test(c)) return Optional.of(e.extraction); + } + return Optional.empty(); + } + + + private static class NodeValueExtractor { + + public final Predicate<Node> applicability; + public final Callback<Node, Object> extraction; + + public NodeValueExtractor( Predicate<Node> applicability, Callback<Node, Object> extraction ) { + this.applicability = Objects.requireNonNull(applicability); + this.extraction = Objects.requireNonNull(extraction); + } + + } + + + private static final List<NodeValueExtractor> valueExtractors = FXCollections.observableArrayList(); + + static { + addValueExtractor( n -> n instanceof CheckBox, cb -> ((CheckBox)cb).isSelected()); + addValueExtractor( n -> n instanceof ChoiceBox, cb -> ((ChoiceBox<?>)cb).getValue()); + addValueExtractor( n -> n instanceof ComboBox, cb -> ((ComboBox<?>)cb).getValue()); + addValueExtractor( n -> n instanceof DatePicker, dp -> ((DatePicker)dp).getValue()); + addValueExtractor( n -> n instanceof RadioButton, rb -> ((RadioButton)rb).isSelected()); + addValueExtractor( n -> n instanceof Slider, sl -> ((Slider)sl).getValue()); + addValueExtractor( n -> n instanceof TextInputControl, ta -> ((TextInputControl)ta).getText()); + + addValueExtractor( n -> n instanceof ListView, lv -> { + MultipleSelectionModel<?> sm = ((ListView<?>)lv).getSelectionModel(); + return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); + }); + addValueExtractor( n -> n instanceof TreeView, tv -> { + MultipleSelectionModel<?> sm = ((TreeView<?>)tv).getSelectionModel(); + return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); + }); + addValueExtractor( n -> n instanceof TableView, tv -> { + MultipleSelectionModel<?> sm = ((TableView<?>)tv).getSelectionModel(); + return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); + }); + addValueExtractor( n -> n instanceof TreeTableView, tv -> { + MultipleSelectionModel<?> sm = ((TreeTableView<?>)tv).getSelectionModel(); + return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); + }); + } + + private ValueExtractor() { + // no-op + } + + public static void addValueExtractor(Predicate<Node> test, Callback<Node, Object> extractor) { + valueExtractors.add(new NodeValueExtractor(test, extractor)); + } + + /** + * Attempts to return a value for the given Node. This is done by checking + * the map of value extractors, contained within this class. This + * map contains value extractors for common UI controls, but more extractors + * can be added by calling {@link #addObservableValueExtractor(Predicate, Callback)}. + * + * @param n The node from whom a value will hopefully be extracted. + * @return The value of the given node. + */ + public static Object getValue(Node n) { + for( NodeValueExtractor nve: valueExtractors ) { + if ( nve.applicability.test(n)) return nve.extraction.call(n); + } + return null; + } +} \ No newline at end of file diff --git a/src/org/controlsfx/tools/package-info.java b/src/org/controlsfx/tools/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..af1fe9a0158b812716080343b22484b39df3cbcf --- /dev/null +++ b/src/org/controlsfx/tools/package-info.java @@ -0,0 +1,4 @@ +/** + * A package containing a number of useful utility methods. + */ +package org.controlsfx.tools; \ No newline at end of file diff --git a/src/org/controlsfx/validation/Severity.java b/src/org/controlsfx/validation/Severity.java new file mode 100644 index 0000000000000000000000000000000000000000..ab51e3e7b4789cb2ba7f18d4c5af7af8f7198bfa --- /dev/null +++ b/src/org/controlsfx/validation/Severity.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation; + +/** + * Defines severity of validation messages + */ +public enum Severity { + + ERROR, + WARNING + +} diff --git a/src/org/controlsfx/validation/SimpleValidationMessage.java b/src/org/controlsfx/validation/SimpleValidationMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..314d67b9d52085a7cb47427e58a6613dec2ea3b3 --- /dev/null +++ b/src/org/controlsfx/validation/SimpleValidationMessage.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation; + +import javafx.scene.control.Control; + +/** + * Internal implementation of simple validation message + */ +class SimpleValidationMessage implements ValidationMessage { + + private final String text; + private final Severity severity; + private final Control target; + + public SimpleValidationMessage( Control target, String text, Severity severity ) { + this.text = text; + this.severity = severity == null? Severity.ERROR: severity; + this.target = target; + } + + @Override public Control getTarget() { + return target; + } + + @Override public String getText() { + return text; + } + + @Override public Severity getSeverity() { + return severity; + } + + @Override public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((severity == null) ? 0 : severity.hashCode()); + result = prime * result + ((target == null) ? 0 : target.hashCode()); + result = prime * result + ((text == null) ? 0 : text.hashCode()); + return result; + } + + @Override public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SimpleValidationMessage other = (SimpleValidationMessage) obj; + if (severity != other.severity) + return false; + if (target == null) { + if (other.target != null) + return false; + } else if (!target.equals(other.target)) + return false; + if (text == null) { + if (other.text != null) + return false; + } else if (!text.equals(other.text)) + return false; + return true; + } + + @Override public String toString() { + return String.format("%s(%s)", severity, text); //$NON-NLS-1$ + } + +} diff --git a/src/org/controlsfx/validation/ValidationMessage.java b/src/org/controlsfx/validation/ValidationMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..4b836d6dbcc402328498b2edc18f66b5a31c375c --- /dev/null +++ b/src/org/controlsfx/validation/ValidationMessage.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation; + +import java.util.Comparator; + +import javafx.scene.control.Control; + +/** + * Interface to define basic contract for validation message + */ +public interface ValidationMessage extends Comparable<ValidationMessage>{ + + public static final Comparator<ValidationMessage> COMPARATOR = new Comparator<ValidationMessage>() { + + @Override + public int compare(ValidationMessage vm1, ValidationMessage vm2) { + if ( vm1 == vm2 ) return 0; + if ( vm1 == null) return 1; + if ( vm2 == null) return -1; + return vm1.compareTo(vm2); + } + }; + + /** + * Message text + * @return message text + */ + public String getText(); + + /** + * Message {@link Severity} + * @return message severity + */ + public Severity getSeverity(); + + + /** + * Message target - {@link Control} which message is related to . + * @return message target + */ + public Control getTarget(); + + /** + * Factory method to create a simple error message + * @param target message target + * @param text message text + * @return error message + */ + public static ValidationMessage error( Control target, String text ) { + return new SimpleValidationMessage(target, text, Severity.ERROR); + } + + /** + * Factory method to create a simple warning message + * @param target message target + * @param text message text + * @return warning message + */ + public static ValidationMessage warning( Control target, String text ) { + return new SimpleValidationMessage(target, text, Severity.WARNING); + } + + @Override default public int compareTo(ValidationMessage msg) { + return msg == null || getTarget() != msg.getTarget() ? -1: getSeverity().compareTo(msg.getSeverity()); + } +} diff --git a/src/org/controlsfx/validation/ValidationResult.java b/src/org/controlsfx/validation/ValidationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..460bb3f98cdcce7051066cdf0649fc491868dda5 --- /dev/null +++ b/src/org/controlsfx/validation/ValidationResult.java @@ -0,0 +1,276 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javafx.scene.control.Control; + +/** + * Validation result. Can generally be thought of a collection of validation messages. + * Allows for quick an painless accumulation of the messages. + * Also provides ability to combine validation results + */ +public class ValidationResult { + + private List<ValidationMessage> errors = new ArrayList<>(); + private List<ValidationMessage> warnings = new ArrayList<>(); + + /** + * Creates empty validation result + */ + public ValidationResult() {} + + /** + * Factory method to create validation result out of one message + * @param target validation target + * @param text message text + * @param severity message severity + * @param condition condition on which message will be added to validation result + * @return New instance of validation result + */ + public static final ValidationResult fromMessageIf( Control target, String text, Severity severity, boolean condition ) { + return new ValidationResult().addMessageIf(target, text, severity, condition); + } + + /** + * Factory method to create validation result out of one error + * @param target validation target + * @param text message text + * @param condition condition on which message will be added to validation result + * @return New instance of validation result + */ + public static final ValidationResult fromErrorIf( Control target, String text, boolean condition ) { + return new ValidationResult().addErrorIf(target, text, condition); + } + + /** + * Factory method to create validation result out of one warning + * @param target validation target + * @param text message text + * @param condition condition on which message will be added to validation result + * @return New instance of validation result + */ + public static final ValidationResult fromWarningIf( Control target, String text, boolean condition ) { + return new ValidationResult().addWarningIf(target, text, condition); + } + + /** + * Factory method to create validation result out of one error + * @param target validation target + * @param text message text + * @return New instance of validation result + */ + public static final ValidationResult fromError( Control target, String text ) { + return fromMessages( ValidationMessage.error(target, text)); + } + + /** + * Factory method to create validation result out of one warning + * @param target validation target + * @param text message text + * @return New instance of validation result + */ + public static final ValidationResult fromWarning( Control target, String text ) { + return fromMessages( ValidationMessage.warning(target, text)); + } + + /** + * Factory method to create validation result out of several messages + * @param messages + * @return New instance of validation result + */ + public static final ValidationResult fromMessages( ValidationMessage... messages ) { + return new ValidationResult().addAll(messages); + } + + /** + * Factory method to create validation result out of collection of messages + * @param messages + * @return New instance of validation result + */ + public static final ValidationResult fromMessages( Collection<? extends ValidationMessage> messages ) { + return new ValidationResult().addAll(messages); + } + + /** + * Factory method to create validation result out of several validation results + * @param results results + * @return New instance of validation result, combining all into one + */ + public static final ValidationResult fromResults( ValidationResult... results ) { + return new ValidationResult().combineAll(results); + } + + /** + * Factory method to create validation result out of collection of validation results + * @param results results + * @return New instance of validation result, combining all into one + */ + public static final ValidationResult fromResults( Collection<ValidationResult> results ) { + return new ValidationResult().combineAll(results); + } + + /** + * Creates a copy of validation result + * @return copy of validation result + */ + public ValidationResult copy() { + return ValidationResult.fromMessages(getMessages()); + } + + /** + * Add one message to validation result + * @param message validation message + * @return updated validation result + */ + public ValidationResult add( ValidationMessage message ) { + + if ( message != null ) { + switch( message.getSeverity() ) { + case ERROR : errors.add( message); break; + case WARNING: warnings.add(message); break; + } + } + + return this; + } + + /** + * Add one message to validation result with condition + * @param target validation target + * @param text message text + * @param severity message severity + * @param condition condition on which message will be added + * @return updated validation result + */ + public ValidationResult addMessageIf( Control target, String text, Severity severity, boolean condition) { + return condition? add( new SimpleValidationMessage(target, text, severity)): this; + } + + /** + * Add one error to validation result with condition + * @param target validation target + * @param text message text + * @param condition condition on which error will be added + * @return updated validation result + */ + public ValidationResult addErrorIf( Control target, String text, boolean condition) { + return addMessageIf(target,text,Severity.ERROR,condition); + } + + /** + * Add one warning to validation result with condition + * @param target validation target + * @param text message text + * @param condition condition on which warning will be added + * @return updated validation result + */ + public ValidationResult addWarningIf( Control target, String text, boolean condition) { + return addMessageIf(target,text,Severity.WARNING,condition); + } + + /** + * Add collection of validation messages + * @param messages + * @return updated validation result + */ + public ValidationResult addAll( Collection<? extends ValidationMessage> messages ) { + messages.stream().forEach( msg-> add(msg)); + return this; + } + + /** + * Add several validation messages + * @param messages + * @return updated validation result + */ + public ValidationResult addAll( ValidationMessage... messages ) { + return addAll(Arrays.asList(messages)); + } + + /** + * Combine validation result with another. This will create a new instance of combined validation result + * @param validationResult + * @return new instance of combined validation result + */ + public ValidationResult combine( ValidationResult validationResult ) { + return validationResult == null? copy(): copy().addAll(validationResult.getMessages()); + } + + /** + * Combine validation result with others. This will create a new instance of combined validation result + * @param validationResults + * @return new instance of combined validation result + */ + public ValidationResult combineAll( Collection<ValidationResult> validationResults ) { + return validationResults.stream().reduce(copy(), (x,r) -> { + return r == null? x: x.addAll(r.getMessages()); + }); + } + + /** + * Combine validation result with others. This will create a new instance of combined validation result + * @param validationResults + * @return new instance of combined validation result + */ + public ValidationResult combineAll( ValidationResult... validationResults ) { + return combineAll( Arrays.asList(validationResults)); + } + + /** + * Retrieve errors represented by validation result + * @return collection of errors + */ + public Collection<ValidationMessage> getErrors() { + return Collections.unmodifiableList(errors); + } + + /** + * Retrieve warnings represented by validation result + * @return collection of warnings + */ + public Collection<ValidationMessage> getWarnings() { + return Collections.unmodifiableList(warnings); + } + + /** + * Retrieve all messages represented by validation result + * @return collection of messages + */ + public Collection<ValidationMessage> getMessages() { + List<ValidationMessage> messages = new ArrayList<>(); + messages.addAll(errors); + messages.addAll(warnings); + return Collections.unmodifiableList(messages); + } + +} diff --git a/src/org/controlsfx/validation/ValidationSupport.java b/src/org/controlsfx/validation/ValidationSupport.java new file mode 100644 index 0000000000000000000000000000000000000000..bd70c0682c4033498237042965efada8e2eadcb7 --- /dev/null +++ b/src/org/controlsfx/validation/ValidationSupport.java @@ -0,0 +1,329 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableMap; +import javafx.collections.ObservableSet; +import javafx.scene.control.Control; +import javafx.util.Callback; + +import org.controlsfx.tools.ValueExtractor; +import org.controlsfx.validation.decoration.GraphicValidationDecoration; +import org.controlsfx.validation.decoration.ValidationDecoration; + +/** + * Provides validation support for UI components. The idea is create an instance of this class the component group, usually a panel.<br> + * Once created, {@link Validator}s can be registered for components, to provide the validation: + * + * <pre> + * ValidationSupport validationSupport = new ValidationSupport(); + * validationSupport.registerValidator(textField, Validator.createEmptyValidator("Text is required")); + * validationSupport.registerValidator(combobox, Validator.createEmptyValidator( "ComboBox Selection required")); + * validationSupport.registerValidator(checkBox, (Control c, Boolean newValue) -> + * ValidationResult.fromErrorIf( c, "Checkbox should be checked", !newValue) + * ); + * </pre> + * + * validationResultProperty provides an ability to react on overall validation result changes: + * <pre> + * validationSupport.validationResultProperty().addListener( (o, oldValue, newValue) -> + messageList.getItems().setAll(newValue.getMessages())); + * </pre> + * + * Standard JavaFX UI controls are supported out of the box. There is also an ability to add support for custom controls. + * To do that "observable value extractor" should be added for specific controls. Such "extractor" consists of two functional interfaces: + * a {@link Predicate} to check the applicability of the control and a {@link Callback} to extract control's observable value. + * Here is an sample of internal registration of such "extractor" for a few controls : + * <pre> + * ValueExtractor.addObservableValueExtractor( c -> c instanceof TextInputControl, c -> ((TextInputControl)c).textProperty()); + * ValueExtractor.addObservableValueExtractor( c -> c instanceof ComboBox, c -> ((ComboBox<?>)c).getValue()); + * </pre> + * + */ +public class ValidationSupport { + + + private static final String CTRL_REQUIRED_FLAG = "$org.controlsfx.validation.required$"; //$NON-NLS-1$ + + /** + * Set control's required flag + * @param c control + * @param required flag + */ + public static void setRequired( Control c, boolean required ) { + c.getProperties().put(CTRL_REQUIRED_FLAG, required); + } + + /** + * Check control's required flag + * @param c control + * @return true if required + */ + public static boolean isRequired( Control c ) { + Object value = c.getProperties().get(CTRL_REQUIRED_FLAG); + return value instanceof Boolean? (Boolean)value: false; + } + + private ObservableSet<Control> controls = FXCollections.observableSet(); + private ObservableMap<Control,ValidationResult> validationResults = + FXCollections.observableMap(new WeakHashMap<>()); + + + private AtomicBoolean dataChanged = new AtomicBoolean(false); + + /** + * Creates validation support instance. <br> + * If initial decoration is desired invoke {@link #initInitialDecoration()}. + */ + public ValidationSupport() { + + validationResultProperty().addListener( (o, oldValue, validationResult) -> { + invalidProperty.set(!validationResult.getErrors().isEmpty()); + redecorate(); + }); + + // notify validation result observers + validationResults.addListener( (MapChangeListener.Change<? extends Control, ? extends ValidationResult> change) -> + validationResultProperty.set(ValidationResult.fromResults(validationResults.values())) + ); + + } + + /** + * Activates the initial decoration of validated controls. <br> + * By default the decoration will only be applied after the first change of one validated controls value. + */ + public void initInitialDecoration() { + dataChanged.set(true); + redecorate(); + } + + /** + * Redecorates all known components + * Only decorations related to validation are affected + */ + // TODO needs optimization + public void redecorate() { + Optional<ValidationDecoration> odecorator = Optional.ofNullable(getValidationDecorator()); + for (Control target : getRegisteredControls()) { + odecorator.ifPresent( decorator -> { + decorator.removeDecorations(target); + decorator.applyRequiredDecoration(target); + if ( dataChanged.get() && isErrorDecorationEnabled()) { + getHighestMessage(target).ifPresent(msg -> decorator.applyValidationDecoration(msg)); + } + }); + } + } + + private BooleanProperty errorDecorationEnabledProperty = new SimpleBooleanProperty(true) { + protected void invalidated() { + redecorate(); + }; + }; + + public BooleanProperty errorDecorationEnabledProperty() { + return errorDecorationEnabledProperty; + } + + public void setErrorDecorationEnabled(boolean enabled) { + errorDecorationEnabledProperty.set(enabled); + } + + private boolean isErrorDecorationEnabled() { + return errorDecorationEnabledProperty.get(); + } + + + + private ReadOnlyObjectWrapper<ValidationResult> validationResultProperty = + new ReadOnlyObjectWrapper<>(); + + + /** + * Retrieves current validation result + * @return validation result + */ + public ValidationResult getValidationResult() { + return validationResultProperty.get(); + } + + /** + * Can be used to track validation result changes + * @return The Validation result property. + */ + public ReadOnlyObjectProperty<ValidationResult> validationResultProperty() { + return validationResultProperty.getReadOnlyProperty(); + } + + private BooleanProperty invalidProperty = new SimpleBooleanProperty(); + + /** + * Returns current validation state. + * @return true if there is at least one error + */ + public Boolean isInvalid() { + return invalidProperty.get(); + } + + /** + * Validation state property + * @return validation state property + */ + public ReadOnlyBooleanProperty invalidProperty() { + return invalidProperty; + } + + + private ObjectProperty<ValidationDecoration> validationDecoratorProperty = + new SimpleObjectProperty<ValidationDecoration>(this, "validationDecorator", new GraphicValidationDecoration()) { //$NON-NLS-1$ + @Override protected void invalidated() { + // when the decorator changes, rerun the decoration to update the visuals immediately. + redecorate(); + } + }; + + /** + * @return The Validation decorator property + */ + public ObjectProperty<ValidationDecoration> validationDecoratorProperty() { + return validationDecoratorProperty; + } + + /** + * Returns current validation decorator + * @return current validation decorator or null if none + */ + public ValidationDecoration getValidationDecorator() { + return validationDecoratorProperty.get(); + } + + /** + * Sets new validation decorator + * @param decorator new validation decorator. Null value is valid - no decoration will occur + */ + public void setValidationDecorator( ValidationDecoration decorator ) { + validationDecoratorProperty.set(decorator); + } + + + /** + * Registers {@link Validator} for specified control with additional possiblity to mark control as required or not. + * @param c control to validate + * @param required true if controls should be required + * @param validator {@link Validator} to be used + * @return true if registration is successful + */ + @SuppressWarnings("unchecked") + public <T> boolean registerValidator( final Control c, boolean required, final Validator<T> validator ) { + + Optional.ofNullable(c).ifPresent( ctrl -> { + ctrl.getProperties().addListener( new MapChangeListener<Object,Object>(){ + + @Override + public void onChanged( + javafx.collections.MapChangeListener.Change<? extends Object, ? extends Object> change) { + + if ( CTRL_REQUIRED_FLAG.equals(change.getKey())) { + redecorate(); + } + } + + }); + }); + + setRequired( c, required ); + + return ValueExtractor.getObservableValueExtractor(c).map( e -> { + + ObservableValue<T> observable = (ObservableValue<T>) e.call(c); + + Consumer<T> updateResults = value -> { + Platform.runLater(() -> validationResults.put(c, validator.apply(c, value))); + }; + + controls.add(c); + + observable.addListener( (o,oldValue,newValue) -> { + dataChanged.set(true); + updateResults.accept(newValue); + }); + updateResults.accept(observable.getValue()); + + return e; + + }).isPresent(); + } + + /** + * Registers {@link Validator} for specified control and makes control required + * @param c control to validate + * @param validator {@link Validator} to be used + * @return true if registration is successful + */ + public <T> boolean registerValidator( final Control c, final Validator<T> validator ) { + return registerValidator(c, true, validator); + } + + /** + * Returns currently registered controls + * @return set of currently registered controls + */ + public Set<Control> getRegisteredControls() { + return Collections.unmodifiableSet(controls); + } + + /** + * Returns optional highest severity message for a control + * @param target control + * @return Optional highest severity message for a control + */ + public Optional<ValidationMessage> getHighestMessage(Control target) { + return Optional.ofNullable(validationResults.get(target)).flatMap( result -> + result.getMessages().stream().max(ValidationMessage.COMPARATOR) + ); + } +} diff --git a/src/org/controlsfx/validation/Validator.java b/src/org/controlsfx/validation/Validator.java new file mode 100644 index 0000000000000000000000000000000000000000..3a7aeaae73e23acd6a9311495c525c4bef0999e5 --- /dev/null +++ b/src/org/controlsfx/validation/Validator.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation; + +import java.util.Collection; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javafx.scene.control.Control; + +/** + * Interface defining the contract for validation of specific component + * This interface is a {@link BiFunction} which when given the control and its current value + * computes the validation result + * + * @param <T> type of the controls value + */ +public interface Validator<T> extends BiFunction<Control, T, ValidationResult> { + + /** + * Combines the given validators into a single Validator instance. + * @param validators the validators to combine + * @return a Validator instance + */ + @SafeVarargs + static <T> Validator<T> combine(Validator<T>... validators) { + return (control, value) -> Stream.of(validators) + .map(validator -> validator.apply(control, value)) + .collect(Collectors.reducing(new ValidationResult(), ValidationResult::combine)); + } + + /** + * Factory method to create a validator, which checks if value exists. + * @param message text of a message to be created if value is invalid + * @param severity severity of a message to be created if value is invalid + * @return new validator + */ + public static <T> Validator<T> createEmptyValidator(final String message, final Severity severity) { + return (c, value) -> { + boolean condition = value instanceof String ? value.toString().trim().isEmpty() : value == null; + return ValidationResult.fromMessageIf(c, message, severity, condition); + }; + } + + /** + * Factory method to create a validator, which checks if value exists. + * Error is created if not if value does not exist + * @param message of a error to be created if value is invalid + * @return new validator + */ + public static <T> Validator<T> createEmptyValidator(final String message) { + return createEmptyValidator(message, Severity.ERROR); + } + + /** + * Factory method to create a validator, which if value exists in the provided collection. + * @param values text of a message to be created if value is not found + * @param severity severity of a message to be created if value is found + * @return new validator + */ + public static <T> Validator<T> createEqualsValidator(final String message, final Severity severity, final Collection<T> values) { + return (c, value) -> ValidationResult.fromMessageIf(c,message,severity, !values.contains(value)); + } + + /** + * Factory method to create a validator, which checks if value exists in the provided collection. + * Error is created if not found + * @param message text of a error to be created if value is not found + * @param values + * @return new validator + */ + public static <T> Validator<T> createEqualsValidator(final String message, final Collection<T> values) { + return createEqualsValidator(message, Severity.ERROR, values); + } + + /** + * Factory method to create a validator, which evaluates the value validity with a given predicate. + * Error is created if the evaluation is <code>false</code>. + * @param message text of a message to be created if value is invalid + * @param predicate the predicate to be used for the value validity evaluation. + * @return new validator + */ + static <T> Validator<T> createPredicateValidator(Predicate<T> predicate, String message) { + return createPredicateValidator(predicate, message, Severity.ERROR); + } + + /** + * Factory method to create a validator, which evaluates the value validity with a given predicate. + * Error is created if the evaluation is <code>false</code>. + * @param message text of a message to be created if value is invalid + * @param predicate the predicate to be used for the value validity evaluation. + * @param severity severity of a message to be created if value is invalid + * @return new validator + */ + static <T> Validator<T> createPredicateValidator(Predicate<T> predicate, String message, Severity severity) { + return (control, value) -> ValidationResult.fromMessageIf( + control, message, + severity, + predicate.test(value) == false); + } + + /** + * Factory method to create a validator, which checks the value against a given regular expression. + * Error is created if the value is <code>null</code> or the value does not match the pattern. + * @param message text of a message to be created if value is invalid + * @param regex the regular expression the value has to match + * @param severity severity of a message to be created if value is invalid + * @return new validator + */ + public static Validator<String> createRegexValidator(final String message, final String regex, final Severity severity) { + return (c, value) -> { + boolean condition = value == null ? true : !Pattern.matches(regex, value); + return ValidationResult.fromMessageIf(c, message, severity, condition); + }; + } + + /** + * Factory method to create a validator, which checks the value against a given regular expression. + * Error is created if the value is <code>null</code> or the value does not match the pattern. + * @param message text of a message to be created if value is invalid + * @param regex the regular expression the value has to match + * @param severity severity of a message to be created if value is invalid + * @return new validator + */ + public static Validator<String> createRegexValidator(final String message, final Pattern regex, final Severity severity) { + return (c, value) -> { + boolean condition = value == null ? true : !regex.matcher(value).matches(); + return ValidationResult.fromMessageIf(c, message, severity, condition); + }; + } +} diff --git a/src/org/controlsfx/validation/decoration/AbstractValidationDecoration.java b/src/org/controlsfx/validation/decoration/AbstractValidationDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..f717852cc77c31e75538827ce683099c2eacb964 --- /dev/null +++ b/src/org/controlsfx/validation/decoration/AbstractValidationDecoration.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation.decoration; + +import java.util.Collection; +import java.util.List; + +import javafx.scene.control.Control; + +import org.controlsfx.control.decoration.Decoration; +import org.controlsfx.control.decoration.Decorator; +import org.controlsfx.validation.ValidationMessage; +import org.controlsfx.validation.ValidationSupport; + +/** + * Implements common functionality for validation decorators. + * This class intended as a base for custom validation decorators + * Custom validation decorator should define only two things: + * how 'validation' and 'required' decorations should be created + * <br> + * See {@link GraphicValidationDecoration} or {@link StyleClassValidationDecoration} for examples of such implementations. + * + */ +public abstract class AbstractValidationDecoration implements ValidationDecoration { + + private static final String VALIDATION_DECORATION = "$org.controlsfx.decoration.vaidation$"; //$NON-NLS-1$ + + private static boolean isValidationDecoration( Decoration decoration) { + return decoration != null && decoration.getProperties().get(VALIDATION_DECORATION) == Boolean.TRUE; + } + + private static void setValidationDecoration( Decoration decoration ) { + if ( decoration != null ) { + decoration.getProperties().put(VALIDATION_DECORATION, Boolean.TRUE); + } + } + + protected abstract Collection<Decoration> createValidationDecorations(ValidationMessage message); + protected abstract Collection<Decoration> createRequiredDecorations(Control target); + + /** + * Removes all validation related decorations from the target + * @param target control + */ + @Override + public void removeDecorations(Control target) { + List<Decoration> decorations = Decorator.getDecorations(target); + if ( decorations != null ) { + // conversion to array is a trick to prevent concurrent modification exception + for ( Decoration d: Decorator.getDecorations(target).toArray(new Decoration[0]) ) { + if (isValidationDecoration(d)) Decorator.removeDecoration(target, d); + } + } + } + + /* + * (non-Javadoc) + * @see org.controlsfx.validation.decorator.ValidationDecorator#applyValidationDecoration(org.controlsfx.validation.ValidationMessage) + */ + @Override + public void applyValidationDecoration(ValidationMessage message) { + createValidationDecorations(message).stream().forEach( d -> decorate( message.getTarget(), d )); + } + + /* + * (non-Javadoc) + * @see org.controlsfx.validation.decorator.ValidationDecorator#applyRequiredDecoration(javafx.scene.control.Control) + */ + @Override + public void applyRequiredDecoration(Control target) { + if ( ValidationSupport.isRequired(target)) { + createRequiredDecorations(target).stream().forEach( d -> decorate( target, d )); + } + } + + private void decorate( Control target, Decoration d ) { + setValidationDecoration(d); // mark as validation specific decoration + Decorator.addDecoration(target, d); + } + +} diff --git a/src/org/controlsfx/validation/decoration/CompoundValidationDecoration.java b/src/org/controlsfx/validation/decoration/CompoundValidationDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..6ba3722a668ce64212952493d56b3c1830330add --- /dev/null +++ b/src/org/controlsfx/validation/decoration/CompoundValidationDecoration.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation.decoration; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javafx.scene.control.Control; + +import org.controlsfx.control.decoration.Decoration; +import org.controlsfx.validation.ValidationMessage; + +/** + * Validation decoration to combine several existing decorations into one. + * Here is example of combining {@link GraphicValidationDecoration} and {@link StyleClassValidationDecoration} + * <br> <br> + * <img src="CompoundValidationDecoration.png" alt="Screenshot of CompoundValidationDecoration"> + * + * + */ +public class CompoundValidationDecoration extends AbstractValidationDecoration { + + private final Set<ValidationDecoration> decorators = new HashSet<>(); + + /** + * Creates an instance of validator using a collection of validators + * @param decorators collection of validators + */ + public CompoundValidationDecoration(Collection<ValidationDecoration> decorators) { + this.decorators.addAll(decorators); + } + + /** + * Creates an instance of validator using a set of validators + * @param decorators set of validators + */ + public CompoundValidationDecoration(ValidationDecoration... decorators) { + this(Arrays.asList(decorators)); + } + + /** + * {@inheritDoc} + */ + @Override + public void applyRequiredDecoration(Control target) { + this.decorators.stream().forEach( d -> d.applyRequiredDecoration(target)); + } + + /** + * {@inheritDoc} + */ + @Override + public void applyValidationDecoration(ValidationMessage message) { + this.decorators.stream().forEach( d -> d.applyValidationDecoration(message)); + } + + @Override + protected Collection<Decoration> createValidationDecorations(ValidationMessage message) { + return Collections.emptyList(); + } + + @Override + protected Collection<Decoration> createRequiredDecorations(Control target) { + return Collections.emptyList(); + } +} diff --git a/src/org/controlsfx/validation/decoration/GraphicValidationDecoration.java b/src/org/controlsfx/validation/decoration/GraphicValidationDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..c405ceadf3970b157f8edd7d2efbacb13cd1a05e --- /dev/null +++ b/src/org/controlsfx/validation/decoration/GraphicValidationDecoration.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation.decoration; + + +import java.util.Arrays; +import java.util.Collection; + +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +import org.controlsfx.control.decoration.Decoration; +import org.controlsfx.control.decoration.GraphicDecoration; +import org.controlsfx.validation.Severity; +import org.controlsfx.validation.ValidationMessage; + +/** + * Validation decorator to decorate validation state using images. + * <br> + * Validation icons are shown in the bottom-left corner of the control as it is seems to be the most + * logical location for such information. + * Required components are marked at the top-left corner with small red triangle. + * Here is example of such decoration + * <br> <br> + * <img src="GraphicValidationDecorationWithTooltip.png" alt="Screenshot of GraphicValidationDecoration"> + * + */ +public class GraphicValidationDecoration extends AbstractValidationDecoration { + + // TODO we shouldn't hardcode this - defer to CSS eventually + + private static final Image ERROR_IMAGE = new Image(GraphicValidationDecoration.class.getResource("/impl/org/controlsfx/control/validation/decoration-error.png").toExternalForm()); //$NON-NLS-1$ + private static final Image WARNING_IMAGE = new Image(GraphicValidationDecoration.class.getResource("/impl/org/controlsfx/control/validation/decoration-warning.png").toExternalForm()); //$NON-NLS-1$ + private static final Image REQUIRED_IMAGE = new Image(GraphicValidationDecoration.class.getResource("/impl/org/controlsfx/control/validation/required-indicator.png").toExternalForm()); //$NON-NLS-1$ + + private static final String SHADOW_EFFECT = "-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);"; //$NON-NLS-1$ + private static final String POPUP_SHADOW_EFFECT = "-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 5, 0, 0, 5);"; //$NON-NLS-1$ + private static final String TOOLTIP_COMMON_EFFECTS = "-fx-font-weight: bold; -fx-padding: 5; -fx-border-width:1;"; //$NON-NLS-1$ + + private static final String ERROR_TOOLTIP_EFFECT = POPUP_SHADOW_EFFECT + TOOLTIP_COMMON_EFFECTS + + "-fx-background-color: FBEFEF; -fx-text-fill: cc0033; -fx-border-color:cc0033;"; //$NON-NLS-1$ + + private static final String WARNING_TOOLTIP_EFFECT = POPUP_SHADOW_EFFECT + TOOLTIP_COMMON_EFFECTS + + "-fx-background-color: FFFFCC; -fx-text-fill: CC9900; -fx-border-color: CC9900;"; //$NON-NLS-1$ + + /** + * Creates default instance + */ + public GraphicValidationDecoration() { + + } + + // TODO write javadoc that users should override these methods to customise + // the error / warning / success nodes to use + protected Node createErrorNode() { + return new ImageView(ERROR_IMAGE); + } + + protected Node createWarningNode() { + return new ImageView(WARNING_IMAGE); + } + + private Node createDecorationNode(ValidationMessage message) { + Node graphic = Severity.ERROR == message.getSeverity() ? createErrorNode() : createWarningNode(); + graphic.setStyle(SHADOW_EFFECT); + Label label = new Label(); + label.setGraphic(graphic); + label.setTooltip(createTooltip(message)); + label.setAlignment(Pos.CENTER); + return label; + } + + protected Tooltip createTooltip(ValidationMessage message) { + Tooltip tooltip = new Tooltip(message.getText()); + tooltip.setOpacity(.9); + tooltip.setAutoFix(true); + tooltip.setStyle( Severity.ERROR == message.getSeverity()? ERROR_TOOLTIP_EFFECT: WARNING_TOOLTIP_EFFECT); + return tooltip; + } + + /** + * {@inheritDoc} + */ + @Override + protected Collection<Decoration> createValidationDecorations(ValidationMessage message) { + return Arrays.asList(new GraphicDecoration(createDecorationNode(message),Pos.BOTTOM_LEFT)); + } + + /** + * {@inheritDoc} + */ + @Override + protected Collection<Decoration> createRequiredDecorations(Control target) { + return Arrays.asList(new GraphicDecoration(new ImageView(REQUIRED_IMAGE),Pos.TOP_LEFT, REQUIRED_IMAGE.getWidth()/2, REQUIRED_IMAGE.getHeight()/2)); + } + + +} diff --git a/src/org/controlsfx/validation/decoration/StyleClassValidationDecoration.java b/src/org/controlsfx/validation/decoration/StyleClassValidationDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..01f0b81e257c86152e21e2fc22d121b346b2fed4 --- /dev/null +++ b/src/org/controlsfx/validation/decoration/StyleClassValidationDecoration.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2014, 2015, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation.decoration; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import javafx.scene.control.Control; + +import org.controlsfx.control.decoration.Decoration; +import org.controlsfx.control.decoration.StyleClassDecoration; +import org.controlsfx.validation.Severity; +import org.controlsfx.validation.ValidationMessage; + +/** + * Validation decorator to decorate component validation state using two + * CSS classes for errors and warnings. + * Here is example of such decoration + * <br> <br> + * <img src="StyleClassValidationDecoration.png" alt="Screenshot of StyleClassValidationDecoration"> + */ +public class StyleClassValidationDecoration extends AbstractValidationDecoration { + + private final String errorClass; + private final String warningClass; + + /** + * Creates a default instance of a decorator + */ + public StyleClassValidationDecoration() { + this(null,null); + } + + /** + * Creates an instance of validator using custom class names + * @param errorClass class name for error decoration + * @param warningClass class name for warning decoration + */ + public StyleClassValidationDecoration(String errorClass, String warningClass) { + this.errorClass = errorClass != null? errorClass : "error"; //$NON-NLS-1$ + this.warningClass = warningClass != null? warningClass : "warning"; //$NON-NLS-1$ + } + + + @Override + protected Collection<Decoration> createValidationDecorations(ValidationMessage message) { + return Arrays.asList(new StyleClassDecoration( Severity.ERROR == message.getSeverity()? errorClass:warningClass)); + } + + @Override + protected Collection<Decoration> createRequiredDecorations(Control target) { + return Collections.emptyList(); + } + +} diff --git a/src/org/controlsfx/validation/decoration/ValidationDecoration.java b/src/org/controlsfx/validation/decoration/ValidationDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..a6fab8c9cd623edac13be4c2d0f677a71e3cf5a3 --- /dev/null +++ b/src/org/controlsfx/validation/decoration/ValidationDecoration.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2014, ControlsFX + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of ControlsFX, any associated website, nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.controlsfx.validation.decoration; + +import javafx.scene.control.Control; + +import org.controlsfx.validation.ValidationMessage; + +/** + * Contract for validation decorators. + * Classes implementing this interface are used for decorating components with error/warning conditions, if such exists. + * They also used for marking 'required' components. + */ +public interface ValidationDecoration { + + /** + * Removes all validation specific decorations from the target control. + * Non-validation specific decorations are left untouched. + * @param target + */ + void removeDecorations(Control target); + + /** + * Applies validation decoration based on a given validation message + * @param message validation message + */ + void applyValidationDecoration(ValidationMessage message); + + + /** + * Applies 'required' decoration to a given control + * @param target control + */ + void applyRequiredDecoration(Control target); +} diff --git a/src/org/controlsfx/validation/decoration/package-info.java b/src/org/controlsfx/validation/decoration/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..de37a2554ae5fefa5a1682b03ca179bf6e624524 --- /dev/null +++ b/src/org/controlsfx/validation/decoration/package-info.java @@ -0,0 +1,4 @@ +/** + * A package containing decoration API specific to the validation framework. + */ +package org.controlsfx.validation.decoration; \ No newline at end of file diff --git a/src/org/controlsfx/validation/package-info.java b/src/org/controlsfx/validation/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..db054a129430c2f8739bce87eca7772a23af6cb1 --- /dev/null +++ b/src/org/controlsfx/validation/package-info.java @@ -0,0 +1,5 @@ +/** + * A package containing validation-related API (that is, API to allow for developers + * to easily validate user input, and to provide visual feedback on the results). + */ +package org.controlsfx.validation; \ No newline at end of file