diff --git a/testar/build.gradle b/testar/build.gradle
index a5eeecf38..2f1a34317 100644
--- a/testar/build.gradle
+++ b/testar/build.gradle
@@ -103,6 +103,8 @@ dependencies {
implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.1'
implementation group: 'com.orientechnologies', name: 'orientdb-graphdb', version: '3.0.34'
+ implementation 'com.jfoenix:jfoenix:8.0.10'
+ implementation 'org.jsoup:jsoup:1.13.1'
implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '5.10.0.202012080955-r'
}
diff --git a/testar/resources/jfx/Untitled.fxml b/testar/resources/jfx/Untitled.fxml
new file mode 100644
index 000000000..f819b39ce
--- /dev/null
+++ b/testar/resources/jfx/Untitled.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/dashboard.fxml b/testar/resources/jfx/dashboard.fxml
new file mode 100644
index 000000000..c91abb538
--- /dev/null
+++ b/testar/resources/jfx/dashboard.fxml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/filter_button.fxml b/testar/resources/jfx/filter_button.fxml
new file mode 100644
index 000000000..5e268a640
--- /dev/null
+++ b/testar/resources/jfx/filter_button.fxml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/flash_feedback.fxml b/testar/resources/jfx/flash_feedback.fxml
new file mode 100644
index 000000000..87e89b62f
--- /dev/null
+++ b/testar/resources/jfx/flash_feedback.fxml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/icons/add.png b/testar/resources/jfx/icons/add.png
new file mode 100644
index 000000000..f03edd454
Binary files /dev/null and b/testar/resources/jfx/icons/add.png differ
diff --git a/testar/resources/jfx/icons/attach.png b/testar/resources/jfx/icons/attach.png
new file mode 100644
index 000000000..6e8992d20
Binary files /dev/null and b/testar/resources/jfx/icons/attach.png differ
diff --git a/testar/resources/jfx/icons/auto.png b/testar/resources/jfx/icons/auto.png
new file mode 100644
index 000000000..b5304cb7f
Binary files /dev/null and b/testar/resources/jfx/icons/auto.png differ
diff --git a/testar/resources/jfx/icons/back.png b/testar/resources/jfx/icons/back.png
new file mode 100644
index 000000000..539f6583a
Binary files /dev/null and b/testar/resources/jfx/icons/back.png differ
diff --git a/testar/resources/jfx/icons/close.png b/testar/resources/jfx/icons/close.png
new file mode 100644
index 000000000..c7e27e458
Binary files /dev/null and b/testar/resources/jfx/icons/close.png differ
diff --git a/testar/resources/jfx/icons/close_white.png b/testar/resources/jfx/icons/close_white.png
new file mode 100644
index 000000000..6f52e6b9b
Binary files /dev/null and b/testar/resources/jfx/icons/close_white.png differ
diff --git a/testar/resources/jfx/icons/filter.png b/testar/resources/jfx/icons/filter.png
new file mode 100644
index 000000000..a9a9f0c16
Binary files /dev/null and b/testar/resources/jfx/icons/filter.png differ
diff --git a/testar/resources/jfx/icons/general.png b/testar/resources/jfx/icons/general.png
new file mode 100644
index 000000000..6d787b527
Binary files /dev/null and b/testar/resources/jfx/icons/general.png differ
diff --git a/testar/resources/jfx/icons/home.png b/testar/resources/jfx/icons/home.png
new file mode 100644
index 000000000..6e6dabc02
Binary files /dev/null and b/testar/resources/jfx/icons/home.png differ
diff --git a/testar/resources/jfx/icons/info.png b/testar/resources/jfx/icons/info.png
new file mode 100644
index 000000000..71c457848
Binary files /dev/null and b/testar/resources/jfx/icons/info.png differ
diff --git a/testar/resources/jfx/icons/misc.png b/testar/resources/jfx/icons/misc.png
new file mode 100644
index 000000000..6cf50a70e
Binary files /dev/null and b/testar/resources/jfx/icons/misc.png differ
diff --git a/testar/resources/jfx/icons/rec.png b/testar/resources/jfx/icons/rec.png
new file mode 100644
index 000000000..4d1417c2b
Binary files /dev/null and b/testar/resources/jfx/icons/rec.png differ
diff --git a/testar/resources/jfx/icons/replay.png b/testar/resources/jfx/icons/replay.png
new file mode 100644
index 000000000..4bbcefc41
Binary files /dev/null and b/testar/resources/jfx/icons/replay.png differ
diff --git a/testar/resources/jfx/icons/settings.png b/testar/resources/jfx/icons/settings.png
new file mode 100644
index 000000000..eb0072af8
Binary files /dev/null and b/testar/resources/jfx/icons/settings.png differ
diff --git a/testar/resources/jfx/icons/settings_state.fxml b/testar/resources/jfx/icons/settings_state.fxml
new file mode 100644
index 000000000..9e01a2593
--- /dev/null
+++ b/testar/resources/jfx/icons/settings_state.fxml
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/icons/settings_white_24dp.svg b/testar/resources/jfx/icons/settings_white_24dp.svg
new file mode 100644
index 000000000..7074fa8ba
--- /dev/null
+++ b/testar/resources/jfx/icons/settings_white_24dp.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/testar/resources/jfx/icons/source.png b/testar/resources/jfx/icons/source.png
new file mode 100644
index 000000000..7ed686503
Binary files /dev/null and b/testar/resources/jfx/icons/source.png differ
diff --git a/testar/resources/jfx/icons/spy.png b/testar/resources/jfx/icons/spy.png
new file mode 100644
index 000000000..6a8526dd1
Binary files /dev/null and b/testar/resources/jfx/icons/spy.png differ
diff --git a/testar/resources/jfx/icons/state.png b/testar/resources/jfx/icons/state.png
new file mode 100644
index 000000000..882508ff0
Binary files /dev/null and b/testar/resources/jfx/icons/state.png differ
diff --git a/testar/resources/jfx/icons/time.png b/testar/resources/jfx/icons/time.png
new file mode 100644
index 000000000..5daa1ddaf
Binary files /dev/null and b/testar/resources/jfx/icons/time.png differ
diff --git a/testar/resources/jfx/icons/view.png b/testar/resources/jfx/icons/view.png
new file mode 100644
index 000000000..51f57b31d
Binary files /dev/null and b/testar/resources/jfx/icons/view.png differ
diff --git a/testar/resources/jfx/icons/whitebox.png b/testar/resources/jfx/icons/whitebox.png
new file mode 100644
index 000000000..51d0a38ad
Binary files /dev/null and b/testar/resources/jfx/icons/whitebox.png differ
diff --git a/testar/resources/jfx/lang_header.fxml b/testar/resources/jfx/lang_header.fxml
new file mode 100644
index 000000000..7b36da6b4
--- /dev/null
+++ b/testar/resources/jfx/lang_header.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/lang_item.fxml b/testar/resources/jfx/lang_item.fxml
new file mode 100644
index 000000000..8e9d99fed
--- /dev/null
+++ b/testar/resources/jfx/lang_item.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/logos/ing.png b/testar/resources/jfx/logos/ing.png
new file mode 100644
index 000000000..ae176c208
Binary files /dev/null and b/testar/resources/jfx/logos/ing.png differ
diff --git a/testar/resources/jfx/logos/marviq.png b/testar/resources/jfx/logos/marviq.png
new file mode 100644
index 000000000..dccf0fdee
Binary files /dev/null and b/testar/resources/jfx/logos/marviq.png differ
diff --git a/testar/resources/jfx/logos/open_university.png b/testar/resources/jfx/logos/open_university.png
new file mode 100644
index 000000000..6905e5feb
Binary files /dev/null and b/testar/resources/jfx/logos/open_university.png differ
diff --git a/testar/resources/jfx/logos/philips.png b/testar/resources/jfx/logos/philips.png
new file mode 100644
index 000000000..28b9e71ae
Binary files /dev/null and b/testar/resources/jfx/logos/philips.png differ
diff --git a/testar/resources/jfx/logos/sogeti.png b/testar/resources/jfx/logos/sogeti.png
new file mode 100644
index 000000000..269c3d394
Binary files /dev/null and b/testar/resources/jfx/logos/sogeti.png differ
diff --git a/testar/resources/jfx/main.fxml b/testar/resources/jfx/main.fxml
new file mode 100644
index 000000000..c761571ce
--- /dev/null
+++ b/testar/resources/jfx/main.fxml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/misc.fxml b/testar/resources/jfx/misc.fxml
new file mode 100644
index 000000000..8c5e62e5c
--- /dev/null
+++ b/testar/resources/jfx/misc.fxml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/new_tag.fxml b/testar/resources/jfx/new_tag.fxml
new file mode 100644
index 000000000..130177187
--- /dev/null
+++ b/testar/resources/jfx/new_tag.fxml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings.fxml b/testar/resources/jfx/settings.fxml
new file mode 100644
index 000000000..6865f84dd
--- /dev/null
+++ b/testar/resources/jfx/settings.fxml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_child.fxml b/testar/resources/jfx/settings_child.fxml
new file mode 100644
index 000000000..f92a5d421
--- /dev/null
+++ b/testar/resources/jfx/settings_child.fxml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_filter.fxml b/testar/resources/jfx/settings_filter.fxml
new file mode 100644
index 000000000..0156e2511
--- /dev/null
+++ b/testar/resources/jfx/settings_filter.fxml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_general.fxml b/testar/resources/jfx/settings_general.fxml
new file mode 100644
index 000000000..c2d856ecb
--- /dev/null
+++ b/testar/resources/jfx/settings_general.fxml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_git.fxml b/testar/resources/jfx/settings_git.fxml
new file mode 100644
index 000000000..7cbea40e2
--- /dev/null
+++ b/testar/resources/jfx/settings_git.fxml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_misc.fxml b/testar/resources/jfx/settings_misc.fxml
new file mode 100644
index 000000000..4a8b4d3c8
--- /dev/null
+++ b/testar/resources/jfx/settings_misc.fxml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_misc_old.fxml b/testar/resources/jfx/settings_misc_old.fxml
new file mode 100644
index 000000000..c72313502
--- /dev/null
+++ b/testar/resources/jfx/settings_misc_old.fxml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_new.fxml b/testar/resources/jfx/settings_new.fxml
new file mode 100644
index 000000000..6e67aa539
--- /dev/null
+++ b/testar/resources/jfx/settings_new.fxml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_repo.fxml b/testar/resources/jfx/settings_repo.fxml
new file mode 100644
index 000000000..266d32022
--- /dev/null
+++ b/testar/resources/jfx/settings_repo.fxml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_section.fxml b/testar/resources/jfx/settings_section.fxml
new file mode 100644
index 000000000..961f95ae0
--- /dev/null
+++ b/testar/resources/jfx/settings_section.fxml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_sonar.fxml b/testar/resources/jfx/settings_sonar.fxml
new file mode 100644
index 000000000..aeef7f03a
--- /dev/null
+++ b/testar/resources/jfx/settings_sonar.fxml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_sonar_project.fxml b/testar/resources/jfx/settings_sonar_project.fxml
new file mode 100644
index 000000000..eda7b373f
--- /dev/null
+++ b/testar/resources/jfx/settings_sonar_project.fxml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_sonar_service.fxml b/testar/resources/jfx/settings_sonar_service.fxml
new file mode 100644
index 000000000..634924a4b
--- /dev/null
+++ b/testar/resources/jfx/settings_sonar_service.fxml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_sonarqube.fxml b/testar/resources/jfx/settings_sonarqube.fxml
new file mode 100644
index 000000000..f99d98afb
--- /dev/null
+++ b/testar/resources/jfx/settings_sonarqube.fxml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_startup.fxml b/testar/resources/jfx/settings_startup.fxml
new file mode 100644
index 000000000..a545b7da2
--- /dev/null
+++ b/testar/resources/jfx/settings_startup.fxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_state.fxml b/testar/resources/jfx/settings_state.fxml
new file mode 100644
index 000000000..2785a55d6
--- /dev/null
+++ b/testar/resources/jfx/settings_state.fxml
@@ -0,0 +1,220 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_time.fxml b/testar/resources/jfx/settings_time.fxml
new file mode 100644
index 000000000..f73934cf5
--- /dev/null
+++ b/testar/resources/jfx/settings_time.fxml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_web.fxml b/testar/resources/jfx/settings_web.fxml
new file mode 100644
index 000000000..673d679ea
--- /dev/null
+++ b/testar/resources/jfx/settings_web.fxml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/settings_widgets.fxml b/testar/resources/jfx/settings_widgets.fxml
new file mode 100644
index 000000000..3246e34f8
--- /dev/null
+++ b/testar/resources/jfx/settings_widgets.fxml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/tag.fxml b/testar/resources/jfx/tag.fxml
new file mode 100644
index 000000000..159f40771
--- /dev/null
+++ b/testar/resources/jfx/tag.fxml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/jfx/whitebox_test_status.fxml b/testar/resources/jfx/whitebox_test_status.fxml
new file mode 100644
index 000000000..68f96c452
--- /dev/null
+++ b/testar/resources/jfx/whitebox_test_status.fxml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testar/resources/logos/ing.png b/testar/resources/logos/ing.png
new file mode 100644
index 000000000..ae176c208
Binary files /dev/null and b/testar/resources/logos/ing.png differ
diff --git a/testar/resources/logos/marviq.png b/testar/resources/logos/marviq.png
new file mode 100644
index 000000000..dccf0fdee
Binary files /dev/null and b/testar/resources/logos/marviq.png differ
diff --git a/testar/resources/logos/open_university.png b/testar/resources/logos/open_university.png
new file mode 100644
index 000000000..6905e5feb
Binary files /dev/null and b/testar/resources/logos/open_university.png differ
diff --git a/testar/resources/logos/philips.png b/testar/resources/logos/philips.png
new file mode 100644
index 000000000..28b9e71ae
Binary files /dev/null and b/testar/resources/logos/philips.png differ
diff --git a/testar/resources/logos/sogeti.png b/testar/resources/logos/sogeti.png
new file mode 100644
index 000000000..269c3d394
Binary files /dev/null and b/testar/resources/logos/sogeti.png differ
diff --git a/testar/resources/settings/01_desktop_generic_pure_random/test.settings b/testar/resources/settings/01_desktop_generic_pure_random/test.settings
index 906e17297..9314d48f5 100644
--- a/testar/resources/settings/01_desktop_generic_pure_random/test.settings
+++ b/testar/resources/settings/01_desktop_generic_pure_random/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
diff --git a/testar/resources/settings/02_webdriver_parabank/test.settings b/testar/resources/settings/02_webdriver_parabank/test.settings
index b246ef37d..c22eb3163 100644
--- a/testar/resources/settings/02_webdriver_parabank/test.settings
+++ b/testar/resources/settings/02_webdriver_parabank/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
diff --git a/testar/resources/settings/desktop_generic/test.settings b/testar/resources/settings/desktop_generic/test.settings
index e51c26110..c74cacbd7 100644
--- a/testar/resources/settings/desktop_generic/test.settings
+++ b/testar/resources/settings/desktop_generic/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
diff --git a/testar/resources/settings/desktop_generic_action_selection/test.settings b/testar/resources/settings/desktop_generic_action_selection/test.settings
index 26f038b13..110236332 100644
--- a/testar/resources/settings/desktop_generic_action_selection/test.settings
+++ b/testar/resources/settings/desktop_generic_action_selection/test.settings
@@ -6,6 +6,14 @@
Mode = Generate
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_generic_all_features/test.settings b/testar/resources/settings/desktop_generic_all_features/test.settings
index 7c4268929..ce69890b9 100644
--- a/testar/resources/settings/desktop_generic_all_features/test.settings
+++ b/testar/resources/settings/desktop_generic_all_features/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_generic_json/test.settings b/testar/resources/settings/desktop_generic_json/test.settings
index e70f2a889..d5aa0d3ee 100644
--- a/testar/resources/settings/desktop_generic_json/test.settings
+++ b/testar/resources/settings/desktop_generic_json/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_generic_statemodel/test.settings b/testar/resources/settings/desktop_generic_statemodel/test.settings
index aa84ae8f5..d6bde106a 100644
--- a/testar/resources/settings/desktop_generic_statemodel/test.settings
+++ b/testar/resources/settings/desktop_generic_statemodel/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = true
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -150,4 +158,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_qlearning/test.settings b/testar/resources/settings/desktop_qlearning/test.settings
index 99d721aad..a9fd5adbd 100644
--- a/testar/resources/settings/desktop_qlearning/test.settings
+++ b/testar/resources/settings/desktop_qlearning/test.settings
@@ -6,6 +6,14 @@
Mode = Generate
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = true
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_simple_stategraph/test.settings b/testar/resources/settings/desktop_simple_stategraph/test.settings
index 8d5725813..bb093e450 100644
--- a/testar/resources/settings/desktop_simple_stategraph/test.settings
+++ b/testar/resources/settings/desktop_simple_stategraph/test.settings
@@ -6,6 +6,14 @@
Mode = Generate
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = true
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_simple_stategraph_eye/test.settings b/testar/resources/settings/desktop_simple_stategraph_eye/test.settings
index 76bcc36e5..b61efb8ac 100644
--- a/testar/resources/settings/desktop_simple_stategraph_eye/test.settings
+++ b/testar/resources/settings/desktop_simple_stategraph_eye/test.settings
@@ -6,6 +6,14 @@
Mode = Generate
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = true
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_simple_stategraph_sikulix/test.settings b/testar/resources/settings/desktop_simple_stategraph_sikulix/test.settings
index 190218781..ec64647c1 100644
--- a/testar/resources/settings/desktop_simple_stategraph_sikulix/test.settings
+++ b/testar/resources/settings/desktop_simple_stategraph_sikulix/test.settings
@@ -6,6 +6,14 @@
Mode = Generate
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = true
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/desktop_swingset2/test.settings b/testar/resources/settings/desktop_swingset2/test.settings
index 0ab650cbd..623faefdf 100644
--- a/testar/resources/settings/desktop_swingset2/test.settings
+++ b/testar/resources/settings/desktop_swingset2/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -149,4 +157,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/webdriver_detect_similarity/test.settings b/testar/resources/settings/webdriver_detect_similarity/test.settings
index 4881ab070..17cda38da 100644
--- a/testar/resources/settings/webdriver_detect_similarity/test.settings
+++ b/testar/resources/settings/webdriver_detect_similarity/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
diff --git a/testar/resources/settings/webdriver_generic/test.settings b/testar/resources/settings/webdriver_generic/test.settings
index 24e8f238e..ef0dad83b 100644
--- a/testar/resources/settings/webdriver_generic/test.settings
+++ b/testar/resources/settings/webdriver_generic/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -186,3 +194,14 @@ TestGenerator = random
MaxReward = 9999999
Discount = .95
RefreshSpyCanvas = 1.0
+
+#################################################################
+# Whitebox test settings
+#################################################################
+
+SonarDockerize = true
+SonarProjectName = Demo
+SonarProjectKey = demo
+GitUrl = https://github.com/ICTU/quality-time
+GitAuthRequired = false
+
diff --git a/testar/resources/settings/webdriver_gwt/test.settings b/testar/resources/settings/webdriver_gwt/test.settings
index f21537367..caab2df45 100644
--- a/testar/resources/settings/webdriver_gwt/test.settings
+++ b/testar/resources/settings/webdriver_gwt/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
diff --git a/testar/resources/settings/webdriver_spy_custom/test.settings b/testar/resources/settings/webdriver_spy_custom/test.settings
index de73b157c..5d051ebe4 100644
--- a/testar/resources/settings/webdriver_spy_custom/test.settings
+++ b/testar/resources/settings/webdriver_spy_custom/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
diff --git a/testar/resources/settings/webdriver_statemodel/test.settings b/testar/resources/settings/webdriver_statemodel/test.settings
index 5ef1cd1b7..999180dd3 100644
--- a/testar/resources/settings/webdriver_statemodel/test.settings
+++ b/testar/resources/settings/webdriver_statemodel/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = true
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -189,3 +197,9 @@ TestGenerator = random
MaxReward = 9999999
Discount = .95
RefreshSpyCanvas = 1.0
+
+SonarDockerize = true
+SonarProjectName = Demo
+SonarProjectKey = demo
+GitUrl = https://github.com/crystal-lang/crystal
+GitAuthRequired = false
diff --git a/testar/resources/settings/winapi_web_generic/test.settings b/testar/resources/settings/winapi_web_generic/test.settings
index 5e8529869..21778f510 100644
--- a/testar/resources/settings/winapi_web_generic/test.settings
+++ b/testar/resources/settings/winapi_web_generic/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/resources/settings/winapi_web_one_drive/test.settings b/testar/resources/settings/winapi_web_one_drive/test.settings
index ea6e46654..a2dcc4c30 100644
--- a/testar/resources/settings/winapi_web_one_drive/test.settings
+++ b/testar/resources/settings/winapi_web_one_drive/test.settings
@@ -6,6 +6,14 @@
Mode = Spy
+#################################################################
+# State model enabled
+#
+# Only applicable for StateGraph, StatdModel and QLearning
+#################################################################
+
+StateModelEnabled = false
+
#################################################################
# Connect to the System Under Test (SUT)
#
@@ -138,4 +146,4 @@ VisualizeActions = false
VisualizeSelectedAction = true
TestGenerator = random
MaxReward = 9999999
-Discount = .95
\ No newline at end of file
+Discount = .95
diff --git a/testar/src/es/upv/staq/testar/FlashFeedbackOld.java b/testar/src/es/upv/staq/testar/FlashFeedbackOld.java
new file mode 100644
index 000000000..f0037fb2e
--- /dev/null
+++ b/testar/src/es/upv/staq/testar/FlashFeedbackOld.java
@@ -0,0 +1,85 @@
+/***************************************************************************************************
+*
+* Copyright (c) 2015, 2016, 2017, 2018 Universitat Politecnica de Valencia - www.upv.es
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+* 1. Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* 2. 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.
+* 3. Neither the name of the copyright holder 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 es.upv.staq.testar;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Label;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+
+/**
+ * Displays a short-time flash message for feedback.
+ *
+ * @author Urko Rueda Molina (alias: urueda)
+ *
+ */
+ public class FlashFeedbackOld extends JDialog implements Runnable {
+
+ private static final long serialVersionUID = -3851564540655407657L;
+
+ private static int FLASH_DURATION = 1000; // ms
+
+ /**
+ * @param title Non-null and non empty text.
+ */
+ private FlashFeedbackOld(String title) {
+ super((JFrame)null, title, false);
+ this.setType(Type.POPUP);
+ this.setUndecorated(true);
+ Label msg = new Label(title);
+ msg.setBackground(Color.BLACK);
+ msg.setForeground(Color.WHITE);
+ this.add(msg);
+ int dimW = (title.length() + 1) * 12;
+ this.setSize(new Dimension(Math.min(dimW, 512), 32));
+ this.setOpacity(0.75f);
+ }
+
+ public static void flash(String title, int duration){
+ if(duration>1000) FLASH_DURATION = duration;
+ new FlashFeedbackOld(title).run();
+ }
+
+ @Override
+ public void run() {
+ this.setVisible(true);
+ synchronized(this){
+ try{
+ this.wait(FLASH_DURATION);
+ } catch (java.lang.InterruptedException e){}
+ }
+ this.setVisible(false);
+ this.dispose();
+ }
+
+}
diff --git a/testar/src/nl/ou/testar/jfx/MainController.java b/testar/src/nl/ou/testar/jfx/MainController.java
new file mode 100644
index 000000000..e925a1455
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/MainController.java
@@ -0,0 +1,122 @@
+package nl.ou.testar.jfx;
+
+import javafx.scene.Parent;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+import nl.ou.testar.jfx.core.NavigationController;
+import nl.ou.testar.jfx.core.NavigationDelegate;
+import nl.ou.testar.jfx.core.ViewController;
+import nl.ou.testar.jfx.dashboard.DashboardController;
+import nl.ou.testar.jfx.misc.MiscController;
+import nl.ou.testar.jfx.settings.SettingsController;
+
+import javafx.scene.control.*;
+import org.testar.monkey.Settings;
+
+public class MainController extends ViewController {
+
+ enum Mode {
+ HOME, SETTINGS, MISC
+ }
+
+ private Mode mode;
+ private String settingsPath;
+
+ private DashboardController dashboardController;
+ private SettingsController settingsController;
+ private MiscController miscController;
+
+ private Stage stage = null;
+
+ public DashboardController getDashboardController() {
+ return dashboardController;
+ }
+
+ public MainController(Settings settings, String settingsPath) {
+ super("Testar", "jfx/main.fxml", settings);
+ this.settingsPath = settingsPath;
+ dashboardController = new DashboardController(settings, settingsPath);
+ settingsController = new SettingsController(settings, settingsPath);
+ miscController = new MiscController("Misc", settings);
+ }
+
+ private void setupMode(Parent view, Mode mode) {
+
+ if (mode != this.mode) {
+// final Label titleLabel = (Label) view.lookup("#titleLabel");
+// final Button btnBack = (Button) view.lookup("#btnBack");
+
+// final BorderPane contentPane = (BorderPane) view.lookup("#contentPane");
+ final AnchorPane contentPane = (AnchorPane) view.lookup("#contentPane");
+ ViewController targetController;
+ switch (mode) {
+ case SETTINGS:
+ targetController = settingsController;
+ break;
+ case MISC:
+ targetController = miscController;
+ break;
+ default: //HOME
+ targetController = dashboardController;
+ break;
+ }
+ final NavigationController navigationController = new NavigationController(targetController);
+ navigationController.startWithDelegate(new NavigationDelegate() {
+ @Override
+ public void onViewControllerActivated(ViewController viewController, Parent view) {
+// titleLabel.setText(viewController.getTitle());
+ contentPane.getChildren().clear();
+ contentPane.getChildren().add(view);
+ AnchorPane.setLeftAnchor(view, 0.0);
+ AnchorPane.setTopAnchor(view, 0.0);
+ AnchorPane.setRightAnchor(view, 0.0);
+ AnchorPane.setBottomAnchor(view, 0.0);
+
+ if (stage != null) {
+ double width = stage.getWidth();
+ double height = stage.getHeight();
+
+ view.setVisible(false);
+ stage.sizeToScene();
+ stage.setWidth(Math.max(width, stage.getWidth()));
+ stage.setHeight(Math.max(height, stage.getHeight()));
+ view.setVisible(true);
+ }
+ }
+ });
+
+// btnBack.setOnAction(event -> {
+// navigationController.navigateBack();
+// });
+
+ this.mode = mode;
+ }
+ }
+
+ @Override
+ public void viewDidLoad(Parent view) {
+ Button btnHome = (Button) view.lookup("#btnHome");
+ Button btnSettings = (Button) view.lookup("#btnSettings");
+ Button btnMisc = (Button) view.lookup("#btnMisc");
+
+ btnHome.setOnAction(event -> {
+ setupMode(view, Mode.HOME);
+
+ });
+
+ btnSettings.setOnAction(event -> {
+ setupMode(view, Mode.SETTINGS);
+ });
+
+// btnMisc.setOnAction(event -> {
+// setupMode(view, Mode.MISC);
+// });
+
+ setupMode(view, Mode.HOME);
+ }
+
+ @Override
+ public void viewDidAppear(Parent view) {
+ stage = (Stage) view.getScene().getWindow();
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/MainControllerDelegate.java b/testar/src/nl/ou/testar/jfx/MainControllerDelegate.java
new file mode 100644
index 000000000..65c3338e6
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/MainControllerDelegate.java
@@ -0,0 +1,7 @@
+package nl.ou.testar.jfx;
+
+import javafx.scene.Parent;
+
+public interface MainControllerDelegate {
+ void setContentView(Parent contentView);
+}
diff --git a/testar/src/nl/ou/testar/jfx/WhiteboxTestStatus.java b/testar/src/nl/ou/testar/jfx/WhiteboxTestStatus.java
new file mode 100644
index 000000000..ec8e19f6b
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/WhiteboxTestStatus.java
@@ -0,0 +1,180 @@
+package nl.ou.testar.jfx;
+
+import javafx.application.Platform;
+import javafx.event.EventHandler;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Label;
+import javafx.scene.control.ProgressBar;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import nl.ou.testar.jfx.dashboard.DashboardDelegate;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.testar.monkey.ConfigTags;
+import org.testar.monkey.Settings;
+import org.testar.settingsdialog.codeanalysis.CodeAnalysisService;
+import org.testar.settingsdialog.codeanalysis.CodeAnalysisServiceImpl;
+import org.testar.settingsdialog.codeanalysis.RepositoryLanguage;
+import org.testar.settingsdialog.codeanalysis.RepositoryLanguageComposition;
+import org.testar.settingsdialog.vcs.GitCredentials;
+import org.testar.settingsdialog.vcs.GitService;
+import org.testar.settingsdialog.vcs.GitServiceImpl;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.nio.file.Path;
+
+public class WhiteboxTestStatus implements ProgressMonitor {
+
+ private Parent view;
+ private Stage mainStage;
+
+ private Label stageLabel;
+ private Label statusLabel;
+ private ProgressBar progressBar;
+ private VBox contentBox;
+
+ private int currentProgress;
+ private int totalProgress;
+
+ private DashboardDelegate dashboardDelegate;
+
+ private String projectName;
+ private String projectKey;
+ private GitCredentials gitCredentials;
+
+ private final static int HEADER_HEIGHT = 56;
+ private final static int ITEM_HEIGHT = 44;
+
+ private final CodeAnalysisService codeAnalysisService = new CodeAnalysisServiceImpl();
+
+ public void start(Stage stage, Settings settings) throws IOException {
+ mainStage = stage;
+ mainStage.hide();
+ Stage whiteboxStage = new Stage(StageStyle.UNDECORATED);
+
+ FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("jfx/whitebox_test_status.fxml"));
+ view = loader.load();
+ whiteboxStage.setScene(new Scene(view));
+ whiteboxStage.show();
+
+ stageLabel = (Label) view.lookup("#procStage");
+ statusLabel = (Label) view.lookup("#procStatus");
+ progressBar = (ProgressBar) view.lookup("#procProgressBar");
+ contentBox = (VBox) view.lookup("#contentBox");
+
+ // Clone GIT repository
+
+ final GitService gitService = new GitServiceImpl();
+ final String repositoryUrl = settings.get(ConfigTags.GitUrl);
+
+ final String branchName = settings.get(ConfigTags.GitBranch, null);
+
+ projectName = settings.get(ConfigTags.SonarProjectName, "Demo");
+ projectKey = settings.get(ConfigTags.SonarProjectKey, "demo");
+
+ if (settings.get(ConfigTags.GitAuthRequired, false)) {
+ gitCredentials = new GitCredentials(settings.get(ConfigTags.GitUsername), settings.get(ConfigTags.GitToken));
+ }
+
+ stageLabel.setText("Cloning repository");
+ new Thread(() -> {
+ Path repositoryPath;
+ if (gitCredentials == null) {
+ repositoryPath = gitService.cloneRepository(repositoryUrl, this, branchName);
+ } else {
+ repositoryPath = gitService.cloneRepository(repositoryUrl, gitCredentials, this, branchName);
+ }
+ System.out.println("...done");
+
+ RepositoryLanguageComposition composition = codeAnalysisService.scanRepository(repositoryPath);
+
+ Platform.runLater(() -> {
+ try {
+ FXMLLoader headerLoader = new FXMLLoader(getClass().getClassLoader().getResource("jfx/lang_header.fxml"));
+ contentBox.getChildren().add(headerLoader.load());
+ whiteboxStage.setHeight(whiteboxStage.getHeight() + HEADER_HEIGHT);
+ for (RepositoryLanguage language : composition.getRepositoryLanguages()) {
+ whiteboxStage.setHeight(whiteboxStage.getHeight() + ITEM_HEIGHT);
+ FXMLLoader itemLoader = new FXMLLoader(getClass().getClassLoader().getResource("jfx/lang_item.fxml"));
+ Parent itemView = itemLoader.load();
+ Label itemLabel = (Label) itemView.lookup("#lang");
+ ProgressBar itemProgressBar = (ProgressBar) itemView.lookup("#ratio");
+ BigDecimal percentage = language.getPercentage();
+ itemLabel.setText(String.format("%d%% %s", percentage.intValue(), language.getSupportedLanguage().getName()));
+ itemProgressBar.setProgress(0.01 * percentage.doubleValue());
+ contentBox.getChildren().add(itemView);
+ }
+ } catch (IOException e) {
+ final Alert alert = new Alert(Alert.AlertType.ERROR, "Cannot start code analysis\n" + e.getMessage());
+ alert.show();
+ stop();
+ e.printStackTrace();
+ }
+
+ stageLabel.setText("All done");
+ statusLabel.setText("Press any key to close");
+ progressBar.setVisible(false);
+
+ whiteboxStage.getScene().addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler() {
+ @Override
+ public void handle(KeyEvent event) {
+ stop();
+ }
+ });
+ });
+ }).start();
+ }
+
+ @Override
+ public void start(int totalTasks) {
+ }
+
+ @Override
+ public void beginTask(String title, int totalWork) {
+ System.out.println("Process started");
+ Platform.runLater(() -> {
+ statusLabel.setText(title);
+ progressBar.setProgress(0);
+ currentProgress = 0;
+ totalProgress = totalWork;
+ });
+ }
+
+ @Override
+ public void update(int completed) {
+ Platform.runLater(() -> {
+ currentProgress += completed;
+ if (totalProgress > 0) {
+ progressBar.setProgress((double)currentProgress / totalProgress);
+ }
+ });
+ }
+
+ @Override
+ public void endTask() {
+ System.out.println("Process finished");
+ Platform.runLater(() -> {
+ progressBar.setProgress(1.0);
+ });
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ public void stop() {
+ if (view == null) {
+ return;
+ }
+
+ final Stage stage = (Stage) view.getScene().getWindow();
+ stage.close();
+ mainStage.show();
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/controls/FlashFeedback.java b/testar/src/nl/ou/testar/jfx/controls/FlashFeedback.java
new file mode 100644
index 000000000..67ea45a47
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/controls/FlashFeedback.java
@@ -0,0 +1,28 @@
+package nl.ou.testar.jfx.controls;
+
+import javafx.animation.Animation;
+import javafx.animation.FadeTransition;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.control.Label;
+import javafx.util.Duration;
+
+import java.io.IOException;
+
+public class FlashFeedback extends Parent {
+ public FlashFeedback(String message) {
+ FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("jfx/flash_feedback.fxml"));
+ try {
+ Parent view = loader.load();
+ Label messageLabel = (Label) view.lookup("#messageText");
+ messageLabel.setText(message);
+
+ FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1.0), messageLabel);
+ fadeTransition.setFromValue(1.0);
+ fadeTransition.setToValue(0.0);
+ fadeTransition.setCycleCount(Animation.INDEFINITE);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/controls/TagControl.java b/testar/src/nl/ou/testar/jfx/controls/TagControl.java
new file mode 100644
index 000000000..5de11b8b0
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/controls/TagControl.java
@@ -0,0 +1,51 @@
+package nl.ou.testar.jfx.controls;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+
+import java.io.IOException;
+
+public class TagControl extends Parent {
+
+ public interface Delegate {
+ void onClose();
+ }
+
+ private Delegate delegate;
+ final String tag;
+
+ public Delegate getDelegate() {
+ return delegate;
+ }
+
+ public void setDelegate(Delegate delegate) {
+ this.delegate = delegate;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public TagControl(String tag) {
+ this.tag = tag;
+ FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("jfx/tag.fxml"));
+ try {
+ Parent view = loader.load();
+ Label tagLabel = (Label) view.lookup("#tagLabel");
+ tagLabel.setText(tag);
+ Button btnClose = (Button) view.lookup("#btnClose");
+ btnClose.setOnAction(event -> {
+ if (delegate != null) {
+ delegate.onClose();
+ }
+ });
+
+ getChildren().add(view);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/controls/TagInput.java b/testar/src/nl/ou/testar/jfx/controls/TagInput.java
new file mode 100644
index 000000000..0183788e6
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/controls/TagInput.java
@@ -0,0 +1,53 @@
+package nl.ou.testar.jfx.controls;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+
+import java.io.IOException;
+
+public class TagInput extends Parent {
+ public interface Delegate {
+ void tagTyped(String tag);
+ }
+
+ private TextField tagInputField;
+ private Delegate delegate;
+
+ public Delegate getDelegate() {
+ return delegate;
+ }
+
+ public void setDelegate(Delegate delegate) {
+ this.delegate = delegate;
+ }
+
+ public TagInput() {
+ FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("jfx/new_tag.fxml"));
+ try {
+ Parent view = loader.load();
+ tagInputField = (TextField) view.lookup("#newTagInput");
+ tagInputField.textProperty().addListener((observable, oldValue, newValue) -> {
+ if (!newValue.matches("\\w*")) {
+ tagInputField.setText(newValue.replaceAll("\\W+", ""));
+ }
+ });
+ tagInputField.setOnKeyPressed(event -> {
+ if(event.getCode().equals(KeyCode.ENTER)) {
+ final String tag = tagInputField.getText();
+ if (delegate != null && tag.length() > 0) {
+ delegate.tagTyped(tag);
+ }
+ }
+ });
+ getChildren().add(view);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void clear() {
+ tagInputField.clear();
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/core/NavigationController.java b/testar/src/nl/ou/testar/jfx/core/NavigationController.java
new file mode 100644
index 000000000..b06d01192
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/core/NavigationController.java
@@ -0,0 +1,107 @@
+package nl.ou.testar.jfx.core;
+
+import javafx.scene.Parent;
+
+import java.io.IOException;
+import java.util.Stack;
+
+public class NavigationController {
+
+ private NavigationDelegate delegate;
+ private Stack viewControllerStack = new Stack<>();
+ private ViewController currentViewController;
+ private ViewController rootViewController;
+
+ public NavigationController(ViewController rootViewController) {
+ this.currentViewController = rootViewController;
+ this.rootViewController = rootViewController;
+ rootViewController.setNavigationController(this);
+ }
+
+ public NavigationDelegate getDelegate() {
+ return delegate;
+ }
+
+ void setDelegate(NavigationDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ public void startWithDelegate(NavigationDelegate delegate) {
+ this.delegate = delegate;
+ try {
+ Parent view = currentViewController.obtainView();
+ currentViewController.viewWillAppear(view);
+ delegate.onViewControllerActivated(currentViewController, view);
+ currentViewController.viewDidAppear(view);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public ViewController getRootViewController() {
+ return rootViewController;
+ }
+
+ public ViewController getCurrentViewController() {
+ return currentViewController;
+ }
+
+ public boolean navigateTo(ViewController viewController, Boolean pushToStack) {
+ //TODO: delegate shouldn't be nil
+ if (!currentViewController.checkBeforeExit()) {
+ return false;
+ }
+
+ try {
+ Parent view = viewController.obtainView();
+ ViewController previousViewController = currentViewController;
+ previousViewController.viewWillDisappear();
+ if (pushToStack) {
+ viewControllerStack.push(currentViewController);
+ }
+ currentViewController = viewController;
+ viewController.setNavigationController(this);
+
+ currentViewController.viewWillAppear(view);
+ delegate.onViewControllerActivated(currentViewController, view);
+ previousViewController.viewDidDisappear();
+ currentViewController.viewDidAppear(view);
+
+ return true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public boolean isBackAvailable() {
+ return !currentViewController.equals(rootViewController);
+ }
+
+ public ViewController navigateBack() throws UnsupportedOperationException {
+ if (currentViewController.equals(rootViewController)) {
+ throw new UnsupportedOperationException("No way back from the root view controller");
+ }
+
+ if (!currentViewController.checkBeforeExit()) {
+ return null;
+ }
+
+ try {
+ ViewController redundantViewController = currentViewController;
+ redundantViewController.viewWillDisappear();
+
+ currentViewController = viewControllerStack.pop();
+
+ Parent view = currentViewController.obtainView();
+ currentViewController.viewWillAppear(view);
+ delegate.onViewControllerActivated(currentViewController, view);
+ redundantViewController.viewDidDisappear();
+ currentViewController.viewDidAppear(view);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return currentViewController;
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/core/NavigationDelegate.java b/testar/src/nl/ou/testar/jfx/core/NavigationDelegate.java
new file mode 100644
index 000000000..3b1f39bbd
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/core/NavigationDelegate.java
@@ -0,0 +1,7 @@
+package nl.ou.testar.jfx.core;
+
+import javafx.scene.Parent;
+
+public interface NavigationDelegate {
+ void onViewControllerActivated(ViewController viewController, Parent view);
+}
diff --git a/testar/src/nl/ou/testar/jfx/core/ViewController.java b/testar/src/nl/ou/testar/jfx/core/ViewController.java
new file mode 100644
index 000000000..1b4407f80
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/core/ViewController.java
@@ -0,0 +1,71 @@
+package nl.ou.testar.jfx.core;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import org.testar.monkey.Settings;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+public abstract class ViewController {
+
+ private WeakReference viewReference = new WeakReference<>(null);
+ private String title;
+ private String resourcePath;
+ protected Settings settings;
+
+ private NavigationController navigationController;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public NavigationController getNavigationController() {
+ return navigationController;
+ }
+
+ public void setNavigationController(NavigationController navigationController) {
+ this.navigationController = navigationController;
+ }
+
+ public ViewController(String title, String resourcePath, Settings settings) {
+ this.title = title;
+ this.resourcePath = resourcePath;
+ this.settings = settings;
+ }
+
+ public Parent obtainView() throws IOException {
+ Parent view = viewReference.get();
+ if (view == null) {
+ FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(resourcePath));
+ view = loader.load();
+ viewDidLoad(view);
+ }
+ viewReference = new WeakReference<>(view);
+ return view;
+ }
+
+ public void viewDidLoad(Parent view) {
+ // To be overridden
+ }
+
+ public void viewWillAppear(Parent view) {
+ // To be overridden
+ }
+
+ public void viewDidAppear(Parent view) {
+ // To be overridden
+ }
+
+ public void viewWillDisappear() {
+ // To be overridden
+ }
+
+ public void viewDidDisappear() {
+ // to be overridden
+ }
+
+ public boolean checkBeforeExit() {
+ return true;
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/dashboard/DashboardController.java b/testar/src/nl/ou/testar/jfx/dashboard/DashboardController.java
new file mode 100644
index 000000000..c62d00415
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/dashboard/DashboardController.java
@@ -0,0 +1,150 @@
+package nl.ou.testar.jfx.dashboard;
+
+import javafx.application.Platform;
+import javafx.scene.Parent;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import nl.ou.testar.jfx.core.ViewController;
+import org.testar.monkey.ConfigTags;
+import org.testar.monkey.RuntimeControlsProtocol;
+import org.testar.monkey.Settings;
+import javafx.scene.image.Image;
+import javafx.scene.layout.HBox;
+import javafx.stage.Stage;
+import nl.ou.testar.jfx.WhiteboxTestStatus;
+import nl.ou.testar.jfx.thirdparty.DisplayShelf;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+public class DashboardController extends ViewController {
+
+ private String settingsPath;
+
+ private DashboardDelegate delegate;
+
+ public DashboardController(Settings settings, String settingsPath) {
+ super("Dashboard", "jfx/dashboard.fxml", settings);
+ this.settingsPath = settingsPath;
+ }
+
+ public DashboardDelegate getDelegate() {
+ return delegate;
+ }
+
+ public void setDelegate(DashboardDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ private void startTesting(Parent view, RuntimeControlsProtocol.Modes mode) {
+ try {
+ checkSettings(settings);
+ settings.set(ConfigTags.Mode, mode);
+ System.out.println("Start testing...");
+ if (delegate != null) {
+ Platform.runLater(() -> new Thread(() -> delegate.startTesting(settings)).start());
+
+ final Stage stage = (Stage) view.getScene().getWindow();
+ }
+ else {
+ System.out.println("No delegate!");
+ }
+ } catch (IllegalStateException ise) {
+ final Alert alert = new Alert(Alert.AlertType.ERROR, ise.getMessage());
+ alert.show();
+ }
+ }
+
+ private void startWhiteboxTesting(Parent view) {
+ final Stage stage = (Stage) view.getScene().getWindow();
+ final WhiteboxTestStatus testStatus = new WhiteboxTestStatus();
+
+ try {
+ testStatus.start(stage, settings);
+ }
+ catch(Exception e) {
+ testStatus.stop();
+ final Alert alert = new Alert(Alert.AlertType.ERROR, "Cannot analyse code\n" + e.getMessage());
+ alert.show();
+
+ e.printStackTrace();
+ }
+ }
+
+ private void checkSettings(Settings settings) throws IllegalStateException {
+ String userInputPattern = settings.get(ConfigTags.ProcessesToKillDuringTest);
+ try {
+ Pattern.compile(userInputPattern);
+ } catch (PatternSyntaxException exception) {
+ throw new IllegalStateException("Your ProcessFilter is not a valid regular expression!");
+ }
+
+ userInputPattern = settings.get(ConfigTags.ClickFilter);
+ try {
+ Pattern.compile(userInputPattern);
+ } catch (PatternSyntaxException exception) {
+ throw new IllegalStateException("Your ClickFilter is not a valid regular expression!");
+ }
+
+ userInputPattern = settings.get(ConfigTags.SuspiciousTitles);
+ try {
+ Pattern.compile(userInputPattern);
+ } catch (PatternSyntaxException exception) {
+ throw new IllegalStateException("Your Oracle is not a valid regular expression!");
+ }
+
+ if (!new File(settings.get(ConfigTags.OutputDir)).exists()) {
+ throw new IllegalStateException("Output Directory does not exist!");
+ }
+ if (!new File(settings.get(ConfigTags.TempDir)).exists()) {
+ throw new IllegalStateException("Temp Directory does not exist!");
+ }
+//
+// settingPanels.forEach((k,v) -> v.right().checkSettings());
+ }
+
+
+ @Override
+ public void viewDidLoad(Parent view) {
+ Button btnSpyMode = (Button) view.lookup("#btnSpyMode");
+ btnSpyMode.setOnAction(event -> {
+ startTesting(view, RuntimeControlsProtocol.Modes.Spy);
+ });
+
+ Button btnAutoTest = (Button) view.lookup("#btnAutoTest");
+ btnAutoTest.setOnAction(event -> {
+ startTesting(view, RuntimeControlsProtocol.Modes.Generate);
+ });
+
+ Button btnRecTest = (Button) view.lookup("#btnRecTest");
+ btnRecTest.setOnAction(event -> {
+ startTesting(view, RuntimeControlsProtocol.Modes.Record);
+ });
+
+ Button btnReplayTest = (Button) view.lookup("#btnReplayTest");
+ btnReplayTest.setOnAction(event -> {
+ startTesting(view, RuntimeControlsProtocol.Modes.Replay);
+ });
+
+ Button btnWhiteboxTest = (Button) view.lookup("#btnWhiteboxTest");
+ btnWhiteboxTest.setOnAction(event -> {
+ startWhiteboxTesting(view);
+ });
+
+ Button btnViewReports = (Button) view.lookup("#btnViewReports");
+ btnViewReports.setOnAction(event -> {
+ startTesting(view, RuntimeControlsProtocol.Modes.View);
+ });
+
+ HBox carouselBox = (HBox) view.lookup("#carouselBox");
+ final String imagePaths[] = {"/logos/ing.png", "/logos/marviq.png", "/logos/open_university.png", "/logos/philips.png", "/logos/sogeti.png"};
+ final Image images[] = Arrays.stream(imagePaths).map(path -> new Image(path)).toArray(Image[]::new);
+ DisplayShelf carouselView = new DisplayShelf(images);
+ carouselBox.getChildren().add(carouselView);
+ carouselView.prefWidthProperty().bind(carouselBox.widthProperty());
+ carouselView.prefHeightProperty().bind(carouselBox.heightProperty());
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/dashboard/DashboardDelegate.java b/testar/src/nl/ou/testar/jfx/dashboard/DashboardDelegate.java
new file mode 100644
index 000000000..98feafbd1
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/dashboard/DashboardDelegate.java
@@ -0,0 +1,7 @@
+package nl.ou.testar.jfx.dashboard;
+
+import org.testar.monkey.Settings;
+
+public interface DashboardDelegate {
+ void startTesting(Settings settings);
+}
diff --git a/testar/src/nl/ou/testar/jfx/misc/MiscController.java b/testar/src/nl/ou/testar/jfx/misc/MiscController.java
new file mode 100644
index 000000000..ea703b9a7
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/misc/MiscController.java
@@ -0,0 +1,10 @@
+package nl.ou.testar.jfx.misc;
+
+import nl.ou.testar.jfx.core.ViewController;
+import org.testar.monkey.Settings;
+
+public class MiscController extends ViewController {
+ public MiscController(String title, Settings settings) {
+ super(title, "jfx/misc.fxml", settings);
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/SettingsController.java b/testar/src/nl/ou/testar/jfx/settings/SettingsController.java
new file mode 100644
index 000000000..e90c7a14d
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/SettingsController.java
@@ -0,0 +1,49 @@
+package nl.ou.testar.jfx.settings;
+
+import javafx.scene.Parent;
+import javafx.scene.control.Button;
+import nl.ou.testar.jfx.core.ViewController;
+import nl.ou.testar.jfx.settings.child.*;
+import org.testar.monkey.ConfigTags;
+import org.testar.monkey.Settings;
+
+public class SettingsController extends ViewController {
+
+ private String settingsPath;
+
+ public SettingsController(Settings settings, String settingsPath) {
+ super("Settings", "jfx/settings_new.fxml", settings);
+ this.settingsPath = settingsPath;
+ }
+
+ @Override
+ public void viewDidLoad(Parent view) {
+ Button btnGeneral = (Button) view.lookup("#btnGeneral");
+ Button btnFilters = (Button) view.lookup("#btnFilters");
+ Button btnTime = (Button) view.lookup("#btnTime");
+ Button btnMisc = (Button) view.lookup("#btnMisc");
+ Button btnWhitebox = (Button) view.lookup("#btnWhitebox");
+ Button btnState = (Button) view.lookup("#btnState");
+
+ btnGeneral.setOnAction(event -> {
+ getNavigationController().navigateTo(new GeneralSettingsController(settings, settingsPath), true);
+ });
+ btnFilters.setOnAction(event -> {
+ getNavigationController().navigateTo(new FilterSettingsController(settings, settingsPath), true);
+ });
+ btnTime.setOnAction(event -> {
+ getNavigationController().navigateTo(new TimeSettingsController(settings, settingsPath), true);
+ });
+ btnMisc.setOnAction(event -> {
+ getNavigationController().navigateTo(new MiscSettingsController(settings, settingsPath), true);
+ });
+ btnWhitebox.setOnAction(event -> {
+ getNavigationController().navigateTo(new WhiteboxSettingsController(settings, settingsPath), true);
+ });
+ btnState.setOnAction(event -> {
+ getNavigationController().navigateTo(new StateSettingsController(settings, settingsPath), true);
+ });
+
+ btnState.setDisable(!settings.get(ConfigTags.StateModelEnabled, false));
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/AbstractConfigBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/AbstractConfigBinding.java
new file mode 100644
index 000000000..8bc441df5
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/AbstractConfigBinding.java
@@ -0,0 +1,31 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.Control;
+
+public abstract class AbstractConfigBinding {
+
+ protected C control;
+ protected T value;
+
+ public AbstractConfigBinding(C control) {
+ this.control = control;
+ }
+
+ public C getControl() {
+ return control;
+ }
+
+ protected abstract T getTargetValue();
+ protected abstract void setTargetValue(T value);
+
+ public void save() {
+ setTargetValue(value);
+ };
+
+ public boolean needsSave() {
+ T targetValue = getTargetValue();
+ return !((value == null && targetValue == null) || value.equals(targetValue));
+ }
+
+ public abstract void onBind();
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/CheckTagBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/CheckTagBinding.java
new file mode 100644
index 000000000..57ef21e3b
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/CheckTagBinding.java
@@ -0,0 +1,19 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.CheckBox;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class CheckTagBinding extends ConfigTagBinding {
+ public CheckTagBinding(Settings settings, CheckBox control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ public void onBind() {
+ control.setSelected(value);
+ control.selectedProperty().addListener((observable, oldValue, newValue) -> {
+ value = newValue;
+ });
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/ComboBoxTagBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/ComboBoxTagBinding.java
new file mode 100644
index 000000000..565569799
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/ComboBoxTagBinding.java
@@ -0,0 +1,19 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.ComboBox;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class ComboBoxTagBinding extends ConfigTagBinding, T> {
+ public ComboBoxTagBinding(Settings settings, ComboBox control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ public void onBind() {
+ control.setValue(value);
+ control.valueProperty().addListener((observable, oldValue, newValue) -> {
+ value = newValue;
+ });
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigBinding.java
new file mode 100644
index 000000000..b0cd47d56
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigBinding.java
@@ -0,0 +1,198 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.*;
+import nl.ou.testar.jfx.settings.bindings.control.*;
+import nl.ou.testar.jfx.settings.bindings.data.DataSource;
+import nl.ou.testar.jfx.settings.bindings.data.TagDataSource;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class ConfigBinding {
+
+ public enum GenericType {
+ FIELD_STRING, FIELD_INT, FIELD_DOUBLE, TEXT_INPUT, CHECK_BOX, COMBO_BOX
+ }
+
+ private ControlBinding controlBinding;
+ private DataSource dataSource;
+
+ private T value;
+
+ private ConfigBinding(ControlBinding controlBinding, DataSource dataSource) {
+ this.controlBinding = controlBinding;
+ this.dataSource = dataSource;
+ this.value = dataSource.getData();
+ }
+
+ public void bind() {
+ controlBinding. setValue(value);
+ controlBinding.onBind(value -> {
+ setValue(value);
+ });
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ controlBinding.setValue(value);
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public T resetValue() {
+ setValue(dataSource.getData());;
+ return value;
+ }
+
+ public boolean needsSave() {
+ T data = dataSource.getData();
+ if (value == null) {
+ return dataSource.getData() != null;
+ }
+ return !value.equals(data);
+ }
+
+ public void save() {
+ dataSource.setData(value);
+ }
+
+
+ public static class Builder {
+ private Control control;
+ private GenericType genericType;
+ private Settings settings;
+ private Tag tag;
+
+ private ControlBinding customControlBinding;
+ private DataSource customDataSource;
+
+ public Builder withControl(Control control) {
+ this.control = control;
+ return this;
+ }
+
+ public Builder withGenericType(GenericType genericType) {
+ this.genericType = genericType;
+ return this;
+ }
+
+ public Builder withSettings(Settings settings) {
+ this.settings = settings;
+ return this;
+ }
+
+ public Builder withTag(Tag tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ public Builder withCustomControlBinding(ControlBinding customControlBinding) {
+ this.customControlBinding = customControlBinding;
+ return this;
+ }
+
+ public Builder withCustomDataSource(DataSource dataSource) {
+ this.customDataSource = dataSource;
+ return this;
+ }
+
+ public ConfigBinding build() throws ConfigBindingException {
+
+ ControlBinding controlBinding = customControlBinding;
+ DataSource dataSource = customDataSource;
+
+ if (dataSource == null) {
+ if (settings == null) {
+ throw new ConfigBindingException("Either settings object or custom data source should be set");
+ }
+ if (tag == null) {
+ throw new ConfigBindingException("Either settings tag or custom data source should be set");
+ }
+
+ TagDataSource tagDataSource = new TagDataSource(settings, tag);
+ if (genericType == GenericType.FIELD_STRING || genericType == GenericType.TEXT_INPUT) {
+ tagDataSource.setDefaultValue("");
+ }
+ else if (genericType == GenericType.FIELD_INT) {
+ tagDataSource.setDefaultValue(new Integer(0));
+ }
+ else if (genericType == GenericType.FIELD_DOUBLE) {
+ tagDataSource.setDefaultValue(new Double(0));
+ }
+ else if (genericType == GenericType.CHECK_BOX) {
+ tagDataSource.setDefaultValue(new Boolean(false));
+ }
+ dataSource = tagDataSource;
+ }
+
+ if (controlBinding == null) {
+ if (control == null) {
+ throw new ConfigBindingException("Control not set");
+ }
+ if (genericType == null) {
+ throw new ConfigBindingException("Either generic type or custom control binding should be set");
+ }
+ switch (genericType) {
+ case TEXT_INPUT:
+ assertControlTypeMatch(control, TextArea.class);
+ assertDataTypeMatch(dataSource, String.class);
+ controlBinding = new TextAreaBinding((TextArea) control);
+ break;
+ case FIELD_STRING:
+ assertControlTypeMatch(control, TextField.class);
+ assertDataTypeMatch(dataSource, String.class);
+ controlBinding = new StringFieldBinding((TextField) control);
+ break;
+ case FIELD_INT:
+ assertControlTypeMatch(control, TextField.class);
+ assertDataTypeMatch(dataSource, Integer.class);
+ controlBinding = new IntegerFieldBinding((TextField) control);
+ break;
+ case FIELD_DOUBLE:
+ assertControlTypeMatch(control, TextField.class);
+ assertDataTypeMatch(dataSource, Double.class);
+ controlBinding = new DoubleFieldBinding((TextField) control);
+ break;
+ case CHECK_BOX:
+ assertControlTypeMatch(control, CheckBox.class);
+ assertDataTypeMatch(dataSource, Boolean.class);
+ controlBinding = new CheckBoxBinding((CheckBox) control);
+ break;
+ case COMBO_BOX:
+ assertControlTypeMatch(control, ComboBox.class);
+ controlBinding = new ComboBoxBinding((ComboBox) control);
+ break;
+ default:
+ throw new ConfigBindingException(String.format("Unknown generic type: %s", genericType.toString()));
+ }
+ }
+
+ ConfigBinding configBinding = new ConfigBinding(controlBinding, dataSource);
+
+ // Reset builder to reuse it
+ control = null;
+ genericType = null;
+ settings = null;
+ tag = null;
+ customControlBinding = null;
+ customDataSource = null;
+
+ return configBinding;
+ }
+ private void assertControlTypeMatch(Control control, Class expectedControlType)
+ throws ConfigBindingException {
+ if (!expectedControlType.isInstance(control)) {
+ throw new ConfigBindingException(String.format("Control type mismatch: expected %s, actual %s",
+ expectedControlType.getSimpleName(), control.getClass().getSimpleName()));
+ }
+ };
+ private void assertDataTypeMatch(DataSource dataSource, Class expectedTagType)
+ throws ConfigBindingException {
+ if (!dataSource.getDataType().isAssignableFrom(expectedTagType)) {
+ throw new ConfigBindingException(String.format("Data type mismatch: expected %s, actual %s",
+ expectedTagType.getSimpleName(), dataSource.getDataType().getSimpleName()));
+ }
+ };
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigBindingException.java b/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigBindingException.java
new file mode 100644
index 000000000..bc57cd5e1
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigBindingException.java
@@ -0,0 +1,7 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+public class ConfigBindingException extends Exception {
+ public ConfigBindingException(String errorMessage) {
+ super(errorMessage);
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigTagBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigTagBinding.java
new file mode 100644
index 000000000..b3ffe177a
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/ConfigTagBinding.java
@@ -0,0 +1,62 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.*;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public abstract class ConfigTagBinding extends AbstractConfigBinding {
+
+ public enum Type {
+ FIELD_STRING, FIELD_INT, FIELD_DOUBLE, TEXT_INPUT, CHECK_BOX, COMBO_BOX
+ }
+
+ protected Settings settings;
+ protected Tag tag;
+
+ public ConfigTagBinding(Settings settings, C control, Tag tag) {
+ super(control);
+ this.settings = settings;
+ this.tag = tag;
+ this.value = settings.get(tag);
+ }
+
+ public Tag getTag() {
+ return tag;
+ }
+
+ @Override
+ protected T getTargetValue() {
+ return settings.get(tag);
+ }
+
+ @Override
+ protected void setTargetValue(T value) {
+ settings.set(tag, value);
+ }
+
+ public static ConfigTagBinding bind(Settings settings, Control control, Tag tag, Type type) {
+ ConfigTagBinding binding;
+ switch (type) {
+ case FIELD_STRING:
+ binding = new StringFieldTagBinding(settings, (TextField) control, tag);
+ break;
+ case FIELD_INT:
+ binding = new IntegerFieldTagBinding(settings, (TextField) control, tag);
+ break;
+ case FIELD_DOUBLE:
+ binding = new DoubleInputBinding(settings, (TextField) control, tag);
+ break;
+ case TEXT_INPUT:
+ binding = new TextInputBinding(settings, (TextInputControl) control, tag);
+ break;
+ case CHECK_BOX:
+ binding = new CheckTagBinding(settings, (CheckBox) control, tag);
+ break;
+ default: // COMBO_BOX
+ binding = new ComboBoxTagBinding(settings, (ComboBox) control, tag);
+ break;
+ }
+ binding.onBind();
+ return binding;
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/DoubleInputBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/DoubleInputBinding.java
new file mode 100644
index 000000000..b786973d1
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/DoubleInputBinding.java
@@ -0,0 +1,29 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.TextField;
+import org.apache.commons.lang.math.NumberUtils;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class DoubleInputBinding extends FieldTagBinding {
+ public DoubleInputBinding(Settings settings, TextField control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ protected String getInputPattern() {
+ // Here we consider only non-negative fixed-point decimal numbers
+ return "\\d*|\\d+\\,\\d*";
+ }
+
+ @Override
+ protected Double valueFromText(String text) {
+ return NumberUtils.toDouble(text, 0);
+ }
+
+ @Override
+ public void onBind() {
+ super.onBind();
+ control.setPromptText("0");
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/FieldTagBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/FieldTagBinding.java
new file mode 100644
index 000000000..c1974590b
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/FieldTagBinding.java
@@ -0,0 +1,43 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.TextField;
+import javafx.scene.control.TextFormatter;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+import java.util.function.UnaryOperator;
+import java.util.regex.Pattern;
+
+public abstract class FieldTagBinding extends ConfigTagBinding {
+
+ protected String getInputPattern() {
+ return null;
+ }
+
+ abstract protected T valueFromText(String text);
+
+ protected String valueToText(T value) {
+ return value.toString();
+ }
+
+ public FieldTagBinding(Settings settings, TextField control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ public void onBind() {
+ control.textProperty().set(valueToText(value));
+ control.textProperty().addListener((observable, oldTextValue, newTextValue) -> {
+ value = valueFromText(newTextValue);
+ });
+
+ String patternString = getInputPattern();
+ if (patternString != null) {
+ Pattern pattern = Pattern.compile(patternString);
+ TextFormatter formatter = new TextFormatter((UnaryOperator) change ->
+ pattern.matcher(change.getControlNewText()).matches() ? change : null);
+
+ control.setTextFormatter(formatter);
+ }
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/IntegerFieldTagBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/IntegerFieldTagBinding.java
new file mode 100644
index 000000000..e87889511
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/IntegerFieldTagBinding.java
@@ -0,0 +1,30 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.TextField;
+import org.apache.commons.lang.math.NumberUtils;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class IntegerFieldTagBinding extends FieldTagBinding {
+
+ public IntegerFieldTagBinding(Settings settings, TextField control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ protected String getInputPattern() {
+ // Here we consider only non-negative decimal numbers
+ return "\\d*";
+ }
+
+ @Override
+ protected Integer valueFromText(String text) {
+ return NumberUtils.toInt(text, 0);
+ }
+
+ @Override
+ public void onBind() {
+ super.onBind();
+ control.setPromptText("0");
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/StringFieldTagBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/StringFieldTagBinding.java
new file mode 100644
index 000000000..acf600421
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/StringFieldTagBinding.java
@@ -0,0 +1,17 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.TextField;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class StringFieldTagBinding extends FieldTagBinding {
+
+ public StringFieldTagBinding(Settings settings, TextField control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ protected String valueFromText(String text) {
+ return text;
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/TextInputBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/TextInputBinding.java
new file mode 100644
index 000000000..4739eb3e4
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/TextInputBinding.java
@@ -0,0 +1,20 @@
+package nl.ou.testar.jfx.settings.bindings;
+
+import javafx.scene.control.TextInputControl;
+import org.testar.monkey.Settings;
+import org.testar.monkey.alayer.Tag;
+
+public class TextInputBinding extends ConfigTagBinding {
+
+ public TextInputBinding(Settings settings, TextInputControl control, Tag tag) {
+ super(settings, control, tag);
+ }
+
+ @Override
+ public void onBind() {
+ control.setText(value);
+ control.textProperty().addListener((observable, oldValue, newValue) -> {
+ value = newValue;
+ });
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/AbstractControlBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/AbstractControlBinding.java
new file mode 100644
index 000000000..5aaff5e32
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/AbstractControlBinding.java
@@ -0,0 +1,12 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.Control;
+
+public abstract class AbstractControlBinding implements ControlBinding {
+
+ protected C control;
+
+ public AbstractControlBinding(C control) {
+ this.control = control;
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/CheckBoxBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/CheckBoxBinding.java
new file mode 100644
index 000000000..1bdbdb4bd
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/CheckBoxBinding.java
@@ -0,0 +1,25 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.CheckBox;
+
+public class CheckBoxBinding extends AbstractControlBinding {
+
+ public CheckBoxBinding(CheckBox control) {
+ super(control);
+ }
+
+ @Override
+ public void setValue(Boolean value) {
+ control.setSelected(value);
+ }
+
+ @Override
+ public Boolean getValue() {
+ return control.isSelected();
+ }
+
+ @Override
+ public void onBind(Callback callback) {
+ control.selectedProperty().addListener((observable, oldValue, newValue) -> callback.onUpdate(newValue));
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/ComboBoxBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/ComboBoxBinding.java
new file mode 100644
index 000000000..7e356dcb3
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/ComboBoxBinding.java
@@ -0,0 +1,33 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.ComboBox;
+
+public class ComboBoxBinding extends AbstractControlBinding, T> {
+
+ public ComboBoxBinding(ComboBox control) {
+ super(control);
+ }
+
+ @Override
+ public void setValue(T value) {
+ int index = 0;
+ for (T currentValue: control.getItems()) {
+ if (currentValue.equals(value)) {
+ control.getSelectionModel().select(index);
+ }
+ index++;
+ }
+ }
+
+ @Override
+ public T getValue() {
+ return control.getValue();
+ }
+
+ @Override
+ public void onBind(Callback callback) {
+ control.valueProperty().addListener((observable, oldValue, newValue) -> {
+ callback.onUpdate(newValue);
+ });
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/ControlBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/ControlBinding.java
new file mode 100644
index 000000000..278a3c07d
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/ControlBinding.java
@@ -0,0 +1,12 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+public interface ControlBinding {
+
+ interface Callback {
+ void onUpdate(S value);
+ }
+
+ void setValue(T value);
+ T getValue();
+ void onBind(Callback callback);
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/DoubleFieldBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/DoubleFieldBinding.java
new file mode 100644
index 000000000..347d29709
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/DoubleFieldBinding.java
@@ -0,0 +1,22 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.TextField;
+import org.apache.commons.lang.math.NumberUtils;
+
+public class DoubleFieldBinding extends FieldBinding {
+
+ public DoubleFieldBinding(TextField control) {
+ super(control);
+ }
+
+ @Override
+ protected Double valueFromText(String text) {
+ return NumberUtils.toDouble(text, 0);
+ }
+
+ @Override
+ public void onBind(Callback callback) {
+ super.onBind(callback);
+ control.setPromptText("0");
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/FieldBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/FieldBinding.java
new file mode 100644
index 000000000..6bb314634
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/FieldBinding.java
@@ -0,0 +1,49 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.TextField;
+import javafx.scene.control.TextFormatter;
+
+import java.util.function.UnaryOperator;
+import java.util.regex.Pattern;
+
+public abstract class FieldBinding extends AbstractControlBinding {
+
+ public FieldBinding(TextField control) {
+ super(control);
+ }
+
+ protected String getInputPattern() {
+ return null;
+ }
+
+ abstract protected T valueFromText(String text);
+
+ protected String valueToText(T value) {
+ return value == null ? "" : value.toString();
+ }
+
+ @Override
+ public void setValue(T value) {
+ control.setText(valueToText(value));
+ }
+
+ @Override
+ public T getValue() {
+ return valueFromText(control.getText());
+ }
+
+ @Override
+ public void onBind(Callback callback) {
+ control.textProperty().addListener((observable, oldValue, newValue) ->
+ callback.onUpdate(valueFromText(newValue)));
+
+ String patternString = getInputPattern();
+ if (patternString != null) {
+ Pattern pattern = Pattern.compile(patternString);
+ TextFormatter formatter = new TextFormatter((UnaryOperator) change ->
+ pattern.matcher(change.getControlNewText()).matches() ? change : null);
+
+ control.setTextFormatter(formatter);
+ }
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/IntegerFieldBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/IntegerFieldBinding.java
new file mode 100644
index 000000000..8891d7dbd
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/IntegerFieldBinding.java
@@ -0,0 +1,22 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.TextField;
+import org.apache.commons.lang.math.NumberUtils;
+
+public class IntegerFieldBinding extends FieldBinding {
+
+ public IntegerFieldBinding(TextField control) {
+ super(control);
+ }
+
+ @Override
+ protected Integer valueFromText(String text) {
+ return NumberUtils.toInt(text, 0);
+ }
+
+ @Override
+ public void onBind(Callback callback) {
+ super.onBind(callback);
+ control.setPromptText("0");
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/StringFieldBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/StringFieldBinding.java
new file mode 100644
index 000000000..40dbbee0f
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/StringFieldBinding.java
@@ -0,0 +1,15 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.TextField;
+
+public class StringFieldBinding extends FieldBinding {
+
+ public StringFieldBinding(TextField control) {
+ super(control);
+ }
+
+ @Override
+ protected String valueFromText(String text) {
+ return text;
+ }
+}
diff --git a/testar/src/nl/ou/testar/jfx/settings/bindings/control/TextAreaBinding.java b/testar/src/nl/ou/testar/jfx/settings/bindings/control/TextAreaBinding.java
new file mode 100644
index 000000000..79ed385ee
--- /dev/null
+++ b/testar/src/nl/ou/testar/jfx/settings/bindings/control/TextAreaBinding.java
@@ -0,0 +1,28 @@
+package nl.ou.testar.jfx.settings.bindings.control;
+
+import javafx.scene.control.TextArea;
+import nl.ou.testar.jfx.settings.bindings.AbstractConfigBinding;
+
+public class TextAreaBinding extends AbstractControlBinding