Commit 53f01ecc3407a1e8b0a5bc7ec5947988e380f5e2

Authored by lsagona
1 parent d06a68ec66
Exists in master and in 1 other branch dev

display message on map

Showing 22 changed files with 191 additions and 100 deletions Side-by-side Diff

src/main/kotlin/application/controller/MapPanelController.kt View file @ 53f01ec
  1 +package application.controller
  2 +
  3 +import application.model.MessageListener
  4 +import application.model.Vessel
  5 +import application.model.observableMessages
  6 +import javafx.concurrent.Worker
  7 +import javafx.fxml.FXML
  8 +import javafx.fxml.Initializable
  9 +import javafx.scene.layout.StackPane
  10 +import map.LeafletMapView
  11 +import map.MapConfig
  12 +import map.displayMessageOnMap
  13 +import java.net.URL
  14 +import java.util.*
  15 +import java.util.concurrent.CompletableFuture
  16 +
  17 +class MapPanelController : Initializable, MessageListener {
  18 +
  19 + @FXML
  20 + private lateinit var map: StackPane
  21 +
  22 + private val mapView = LeafletMapView()
  23 +
  24 +
  25 + override fun initialize(location: URL?, resources: ResourceBundle?) {
  26 + val completeFutureMap: CompletableFuture<Worker.State> = mapView.displayMap(MapConfig())
  27 + observableMessages.listeners.add(this)
  28 +
  29 + /*completeFutureMap.whenComplete{
  30 + workerState, _ ->
  31 + if (workerState == Worker.State.SUCCEEDED) {
  32 + }
  33 + }*/
  34 + map.children.add(mapView)
  35 + }
  36 +
  37 + override fun onValueChanged(newValue: MutableMap<Int?, Vessel>) {
  38 + displayMessageOnMap(mapView)
  39 + }
  40 +
  41 +
  42 +}
src/main/kotlin/application/controller/MenuBarController.kt View file @ 53f01ec
1 1 package application.controller
2 2  
3   -import application.App
4 3 import application.model.createVesselCollection
5 4 import application.model.observableMessages
6 5 import javafx.event.EventHandler
7 6 import javafx.fxml.FXML
8   -import javafx.fxml.FXMLLoader
9 7 import javafx.fxml.Initializable
10   -import javafx.scene.Parent
11 8 import javafx.scene.control.MenuBar
12 9 import javafx.scene.control.MenuItem
13 10 import javafx.stage.FileChooser
src/main/kotlin/application/model/ObservableVessel.kt View file @ 53f01ec
1 1 package application.model
2 2  
  3 +import java.util.*
3 4 import kotlin.properties.Delegates
4 5  
5 6 class ObservableVessel {
src/main/kotlin/application/model/Vessel.kt View file @ 53f01ec
1 1 package application.model
2 2  
3 3 import java.time.LocalDateTime
  4 +import java.util.*
4 5  
5 6  
6   -class Vessel() {
7   - val messages: MutableMap<LocalDateTime, Message> = mutableMapOf()
  7 +class Vessel {
  8 + val messages: SortedMap<LocalDateTime, Message> = sortedMapOf()
8 9 }
src/main/kotlin/application/model/VesselGenerator.kt View file @ 53f01ec
1 1 package application.model
2 2  
3 3 import java.io.File
  4 +import java.util.*
  5 +import kotlin.collections.ArrayList
4 6  
5   -fun createVesselCollection(file: File) : MutableMap<Int?, Vessel> {
  7 +fun createVesselCollection(file: File) : SortedMap<Int, Vessel> {
6 8 val messages : ArrayList<Message> = arrayListOf()
7   - val vessels: MutableMap<Int?, Vessel> = mutableMapOf()
  9 + val vessels: SortedMap<Int, Vessel> = sortedMapOf()
8 10  
9 11 file.forEachLine {
10 12 val arrayMessage = it.split(",")
src/main/kotlin/map/Circle.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 import javafx.scene.paint.Color
4 4  
src/main/kotlin/map/CircleMarkerGenerator.kt View file @ 53f01ec
  1 +package map
  2 +
  3 +import application.model.observableMessages
  4 +
  5 +fun clearMap(map: LeafletMapView) {
  6 + map.clearAllLayer()
  7 +}
  8 +
  9 +fun displayMessageOnMap(map: LeafletMapView) {
  10 + observableMessages.vessels.forEach { (_, value) ->
  11 + value.messages.forEach { (_, message) ->
  12 + map.execScript("L.circleMarker([${message.latitude}, ${message.longitude}], {renderer: myRenderer, radius: 0.01}).addTo(myMap)")
  13 + }
  14 + }
  15 +}
src/main/kotlin/map/ColorMarker.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Enumeration for all marker colors of the leaflet-color-markers JavaScript library.
src/main/kotlin/map/ControlPosition.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Enumeration for all possible map control positions.
src/main/kotlin/map/LatLong.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Immutable value class for defining a geo position.
src/main/kotlin/map/LeafletMapView.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3   -import fdit.leafletmap.events.*
4   -import fdit.leafletmap.events.MapClickEventMaker
5   -import fdit.leafletmap.events.MapMoveEventMaker
6   -import fdit.leafletmap.events.MarkerClickEventMaker
7 3 import javafx.concurrent.Worker
8 4 import javafx.scene.layout.StackPane
9 5 import javafx.scene.paint.Color
10 6 import javafx.scene.shape.Polygon
11 7 import javafx.scene.web.WebEngine
12 8 import javafx.scene.web.WebView
13   -import fdit.leafletmap.events.*
  9 +import map.events.*
14 10 import netscape.javascript.JSObject
15 11 import java.io.ByteArrayOutputStream
16 12 import java.io.File
17 13  
18 14  
19 15  
20 16  
21 17  
22 18  
23 19  
... ... @@ -83,50 +79,51 @@
83 79 }
84 80  
85 81 val jsLayers = mapConfig.layers
86   - .mapIndexed { i, layer -> "'${layer.displayName}': layer${i + 1}" }
87   - .joinToString(", ")
  82 + .mapIndexed { i, layer -> "'${layer.displayName}': layer${i + 1}" }
  83 + .joinToString(", ")
88 84 execScript("var baseMaps = { $jsLayers };")
89 85  
90 86 // execute script for map view creation (Leaflet attribution must not be a clickable link)
91   - execScript("""
  87 + execScript(
  88 + """
92 89 |var myMap = L.map('map', {
93 90 | center: new L.LatLng(${mapConfig.initialCenter.latitude}, ${mapConfig.initialCenter.longitude}),
94 91 | zoom: 5,
95 92 | zoomControl: false,
96 93 | layers: [layer1]
97 94 |});
98   - |
99   - |var markersGroup = L.featureGroup();
100   - |myMap.addLayer(markersGroup);
101   - |var trackGroup = L.featureGroup();
102   - |myMap.addLayer(trackGroup);
103   - |
104   - |myMap.addEventListener("contextmenu", function(e){});
105   - |var attribution = myMap.attributionControl;
106   - |attribution.setPrefix('Leaflet');""".trimMargin())
  95 + |L.control.scale().addTo(mymap);
  96 + |var myRenderer = L.canvas({ padding: 0.5 });""".trimMargin()
  97 + )
107 98  
108   - eventZoomChangeIcon()
  99 +// eventZoomChangeIcon()
109 100  
110 101 // execute script for layer control definition if there are multiple layers
111 102 if (mapConfig.layers.size > 1) {
112   - execScript("""
  103 + execScript(
  104 + """
113 105 |var overlayMaps = {};
114   - |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin())
  106 + |L.control.layers(baseMaps, overlayMaps).addTo(myMap);""".trimMargin()
  107 + )
115 108  
116 109 }
117 110  
118 111 // execute script for scale control definition
119 112 if (mapConfig.scaleControlConfig.show) {
120   - execScript("L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " +
121   - "metric: ${mapConfig.scaleControlConfig.metric}, " +
122   - "imperial: ${!mapConfig.scaleControlConfig.metric}})" +
123   - ".addTo(myMap);")
  113 + execScript(
  114 + "L.control.scale({position: '${mapConfig.scaleControlConfig.position.positionName}', " +
  115 + "metric: ${mapConfig.scaleControlConfig.metric}, " +
  116 + "imperial: ${!mapConfig.scaleControlConfig.metric}})" +
  117 + ".addTo(myMap);"
  118 + )
124 119 }
125 120  
126 121 // execute script for zoom control definition
127 122 if (mapConfig.zoomControlConfig.show) {
128   - execScript("L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" +
129   - ".addTo(myMap);")
  123 + execScript(
  124 + "L.control.zoom({position: '${mapConfig.zoomControlConfig.position.positionName}'})" +
  125 + ".addTo(myMap);"
  126 + )
130 127 }
131 128 }
132 129  
... ... @@ -137,7 +134,7 @@
137 134 * @param zoomLevel zoom level (0 - 19 for OpenStreetMap)
138 135 */
139 136 fun setView(position: LatLong, zoomLevel: Int) =
140   - execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);")
  137 + execScript("myMap.setView([${position.latitude}, ${position.longitude}], $zoomLevel);")
141 138  
142 139 /**
143 140 * Pans the map to the specified geographical center position.
... ... @@ -145,7 +142,7 @@
145 142 * @param position map center position
146 143 */
147 144 fun panTo(position: LatLong) =
148   - execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);")
  145 + execScript("myMap.panTo([${position.latitude}, ${position.longitude}]);")
149 146  
150 147 /**
151 148 * Sets the zoom of the map to the specified level.
... ... @@ -153,7 +150,7 @@
153 150 * @param zoomLevel zoom level (0 - 19 for OpenStreetMap)
154 151 */
155 152 fun setZoom(zoomLevel: Int) =
156   - execScript("myMap.setZoom([$zoomLevel]);")
  153 + execScript("myMap.setZoom([$zoomLevel]);")
157 154  
158 155 /**
159 156 * Adds a Marker Object to a map
... ... @@ -201,11 +198,12 @@
201 198 }
202 199  
203 200 fun setEventMousePosition() {
204   - execScript("var lat=0.0, lng=0.0;\n" +
205   - "myMap.addEventListener('mousemove', function(ev) {\n" +
206   - " lat = ev.latlng.lat;\n" +
207   - " lng = ev.latlng.lng;\n" +
208   - "});"
  201 + execScript(
  202 + "var lat=0.0, lng=0.0;\n" +
  203 + "myMap.addEventListener('mousemove', function(ev) {\n" +
  204 + " lat = ev.latlng.lat;\n" +
  205 + " lng = ev.latlng.lng;\n" +
  206 + "});"
209 207 )
210 208 }
211 209  
... ... @@ -222,11 +220,13 @@
222 220 * @param iconUrl the url if the marker icon
223 221 */
224 222 fun addCustomMarker(markerName: String, iconUrl: String): String {
225   - execScript("var $markerName = L.icon({\n" +
226   - "iconUrl: '${createImage(iconUrl, "png")}',\n" +
227   - "iconSize: [24, 24],\n" +
228   - "iconAnchor: [12, 12],\n" +
229   - "});")
  223 + execScript(
  224 + "var $markerName = L.icon({\n" +
  225 + "iconUrl: '${createImage(iconUrl, "png")}',\n" +
  226 + "iconSize: [24, 24],\n" +
  227 + "iconAnchor: [12, 12],\n" +
  228 + "});"
  229 + )
230 230 return markerName
231 231 }
232 232  
233 233  
234 234  
235 235  
236 236  
237 237  
238 238  
... ... @@ -315,29 +315,42 @@
315 315 fun addTrack(positions: List<LatLong>) {
316 316  
317 317 val jsPositions = positions
318   - .map { " [${it.latitude}, ${it.longitude}]" }
319   - .joinToString(", \n")
  318 + .map { " [${it.latitude}, ${it.longitude}]" }
  319 + .joinToString(", \n")
320 320  
321   - execScript("""
  321 + execScript(
  322 + """
322 323 |var latLngs = [
323 324 |$jsPositions
324 325 |];
325   - |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin())
  326 + |var polyline = L.polyline(latLngs, {color: 'red', weight: 2}).addTo(myMap);""".trimMargin()
  327 + )
326 328 }
327 329  
  330 + fun clearAllLayer() {
  331 + execScript("""
  332 + myMap.eachLayer(function (layer) {
  333 + map.removeLayer(layer);
  334 + });
  335 + """.trimIndent())
  336 + }
  337 +
328 338 fun addTrack(positions: List<LatLong>, id: String, color: Color, tooltip: String) {
329 339  
330 340 val jsPositions = positions
331   - .map { " [${it.latitude}, ${it.longitude}]" }
332   - .joinToString(", \n")
  341 + .map { " [${it.latitude}, ${it.longitude}]" }
  342 + .joinToString(", \n")
333 343  
334 344 val cleanTooltip = tooltip.replace("'", "&apos;")
335   - execScript("""
  345 + execScript(
  346 + """
336 347 |var latLngs = [
337 348 |$jsPositions
338 349 |];
339   - |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255).toInt()},${Math.floor(color.getBlue() * 255).toInt()})";
340   - |var polyline$id = L.polyline(latLngs, {color: color, weight: 2, zIndexOffset: 200}).bindTooltip('$cleanTooltip', {sticky: true}).addTo(trackGroup)""".trimMargin())
  350 + |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255)
  351 + .toInt()},${Math.floor(color.getBlue() * 255).toInt()})";
  352 + |var polyline$id = L.polyline(latLngs, {color: color, weight: 2, zIndexOffset: 200}).bindTooltip('$cleanTooltip', {sticky: true}).addTo(trackGroup)""".trimMargin()
  353 + )
341 354 }
342 355  
343 356 fun makeVesselTrackTransparent(id: String) {
... ... @@ -353,7 +366,8 @@
353 366 }
354 367  
355 368 fun eventZoomChangeIcon() {
356   - execScript("""
  369 + execScript(
  370 + """
357 371 |myMap.on('zoomend', function() {
358 372 |var currentZoom = myMap.getZoom();
359 373 |if (currentZoom < $zoomLimitSmallMarker) {
... ... @@ -366,7 +380,8 @@
366 380 |});
367 381 |}
368 382 |});
369   - """.trimMargin())
  383 + """.trimMargin()
  384 + )
370 385 }
371 386  
372 387 fun removeTrack(id: String) {
373 388  
374 389  
... ... @@ -393,15 +408,18 @@
393 408 }
394 409  
395 410 val jsPositions = latLongs
396   - .map { " [${it.latitude}, ${it.longitude}]" }
397   - .joinToString(", \n")
  411 + .map { " [${it.latitude}, ${it.longitude}]" }
  412 + .joinToString(", \n")
398 413 val idSanitized = id.replace("-", "")
399   - execScript("""
  414 + execScript(
  415 + """
400 416 |var latLngs = [
401 417 |$jsPositions
402 418 |];
403   - |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255).toInt()},${Math.floor(color.getBlue() * 255).toInt()})";
404   - |var polygon$idSanitized = L.polygon(latLngs, {color: color}).addTo(myMap);""".trimMargin())
  419 + |var color = "rgb(${Math.floor(color.getRed() * 255).toInt()} ,${Math.floor(color.getGreen() * 255)
  420 + .toInt()},${Math.floor(color.getBlue() * 255).toInt()})";
  421 + |var polygon$idSanitized = L.polygon(latLngs, {color: color}).addTo(myMap);""".trimMargin()
  422 + )
405 423  
406 424 }
407 425  
src/main/kotlin/map/MapConfig.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Class for defining the layers and controls in the map to be shown.
src/main/kotlin/map/MapLayer.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Enumeration for all supported map layers.
src/main/kotlin/map/Marker.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3   -import fdit.gui.graphicalScenarioEditor.GraphicalScenarioEditorContext
4   -import fdit.gui.utils.tooltip.VesselTooltipUtils.formatVesselSnapshotTooltip
5   -import fdit.metamodel.vessel.Vessel
  3 +import application.model.Vessel
6 4  
  5 +
7 6 /**
8 7 * Creates a marker at the specified geographical position.
9 8 *
10 9  
11 10  
... ... @@ -24,13 +23,17 @@
24 23 private var tooltip = ""
25 24 private var rotation = 0
26 25 private lateinit var aircraft: Vessel
27   - private lateinit var context: GraphicalScenarioEditorContext
28 26 private var relativeDate: Double = 0.0
29 27  
30 28  
31   - constructor(position: LatLong, aircraft: Vessel, relativeDate: Double, context: GraphicalScenarioEditorContext, aircraftIcon: String, zIndexOffset: Int) : this(position, zIndexOffset){
  29 + constructor(
  30 + position: LatLong,
  31 + aircraft: Vessel,
  32 + relativeDate: Double,
  33 + aircraftIcon: String,
  34 + zIndexOffset: Int
  35 + ) : this(position, zIndexOffset) {
32 36 this.aircraft = aircraft
33   - this.context = context
34 37 this.relativeDate = relativeDate
35 38 this.marker = aircraftIcon
36 39 }
... ... @@ -45,7 +48,8 @@
45 48 this.name = nextMarkerName
46 49 this.map = map
47 50 this.attached = true
48   - map.execScript("""
  51 + map.execScript(
  52 + """
49 53 |var currentZoom = myMap.getZoom();
50 54 |var $name;
51 55 |if (currentZoom < ${map.zoomLimitSmallMarker}) {
52 56  
... ... @@ -53,17 +57,16 @@
53 57 |} else {
54 58 |$name = L.marker([${position.latitude}, ${position.longitude}], {title: '', icon: $marker, zIndexOffset: $zIndexOffset}).addTo(markersGroup);
55 59 |}
56   - """.trimMargin())
57   - setTooltip()
  60 + """.trimMargin()
  61 + )
  62 +// setTooltip()
58 63 if (clickable) {
59 64 setClickable()
60 65 }
61 66 }
62 67  
63 68 fun setTooltip() {
64   - this.tooltip = formatVesselSnapshotTooltip(aircraft,
65   - context.getGraphicalScenario().getRecording(),
66   - relativeDate)
  69 + this.tooltip = "TODO"
67 70 this.tooltip = tooltip.replace("\n", "<br>")
68 71 this.tooltip = tooltip.replace("'", "&apos;")
69 72 map.execScript("$name.bindTooltip('<div id=\"html_c92f9552ec164f36978869550cb44ffe\" style=\"width: 100.0%; height: 100.0%;\">${this.tooltip}</div>');")
src/main/kotlin/map/ScaleControlConfig.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Class for defining the scale control of the map. The scale can show either metric or imperial units.
... ... @@ -6,7 +6,8 @@
6 6 * @author Stefan Saring
7 7 */
8 8 class ScaleControlConfig @JvmOverloads constructor(
9   - val show: Boolean = false,
10   - val position: ControlPosition = ControlPosition.BOTTOM_LEFT,
11   - val metric: Boolean = true)
  9 + val show: Boolean = false,
  10 + val position: ControlPosition = ControlPosition.BOTTOM_LEFT,
  11 + val metric: Boolean = true
  12 +)
src/main/kotlin/map/Zone.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
  2 +
  3 +import map.LatLong
  4 +import map.LeafletMapView
2 5  
3 6 class Zone constructor(private var title: String) {
4 7 private lateinit var map: LeafletMapView
src/main/kotlin/map/ZoomControlConfig.kt View file @ 53f01ec
1   -package fdit.leafletmap
  1 +package map
2 2  
3 3 /**
4 4 * Class for defining the zoom control of the map.
... ... @@ -6,6 +6,7 @@
6 6 * @author Stefan Saring
7 7 */
8 8 class ZoomControlConfig @JvmOverloads constructor(
9   - val show: Boolean = true,
10   - val position: ControlPosition = ControlPosition.TOP_LEFT)
  9 + val show: Boolean = true,
  10 + val position: ControlPosition = ControlPosition.TOP_LEFT
  11 +)
src/main/kotlin/map/events/MapClickEvent.kt View file @ 53f01ec
1   -package fdit.leafletmap.events
  1 +package map.events
2 2  
3   -import fdit.leafletmap.LatLong
  3 +import map.LatLong
4 4 import java.util.*
5 5  
6 6 /**
src/main/kotlin/map/events/MapMoveEvent.kt View file @ 53f01ec
1   -package fdit.leafletmap.events
  1 +package map.events
2 2  
3   -import fdit.leafletmap.LatLong
  3 +import map.LatLong
4 4 import java.util.*
5 5  
6 6 /**
src/main/kotlin/map/events/MarkerClickEvent.kt View file @ 53f01ec
1   -package fdit.leafletmap.events
  1 +package map.events
2 2  
3 3 import java.util.*
4 4  
src/main/resources/gui/mapPanel.fxml View file @ 53f01ec
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +
  3 +<?import javafx.scene.layout.StackPane?>
  4 +
  5 +<StackPane prefHeight="150.0" prefWidth="200.0" xmlns="http://javafx.com/javafx"
  6 + xmlns:fx="http://javafx.com/fxml"
  7 + fx:controller="application.controller.MapPanelController"
  8 + fx:id="map"/>
src/main/resources/gui/windows.fxml View file @ 53f01ec
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2  
3   -
4 3 <?import javafx.scene.control.SplitPane?>
5 4 <?import javafx.scene.layout.*?>
6 5 <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="900.0"
7   - prefWidth="1200.0" xmlns="http://javafx.com/javafx/8.0.241" xmlns:fx="http://javafx.com/fxml/1">
  6 + prefWidth="1200.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
8 7 <children>
9 8 <fx:include source="menuBar.fxml" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
10 9 AnchorPane.topAnchor="0.0"/>
11 10  
... ... @@ -20,11 +19,11 @@
20 19 </AnchorPane>
21 20 <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0">
22 21 <children>
23   - <SplitPane dividerPositions="0.5" layoutX="127.0" layoutY="74.0" orientation="VERTICAL"
  22 + <SplitPane dividerPositions="0.536" layoutX="127.0" layoutY="74.0" orientation="VERTICAL"
24 23 prefHeight="200.0" prefWidth="160.0" AnchorPane.bottomAnchor="0.0"
25 24 AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
26 25 <items>
27   - <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0"/>
  26 + <fx:include source="mapPanel.fxml" />
28 27 <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="100.0" prefWidth="160.0"/>
29 28 </items>
30 29 </SplitPane>