From 6a4c1ace4ff6beb063b8e211e3c597125934c09d Mon Sep 17 00:00:00 2001 From: Andreas Billmann Date: Mon, 23 Mar 2015 21:50:58 +0100 Subject: [PATCH] initial commit of version 2 --- .gitignore | 76 ++++ README.md | 17 + build.gradle | 26 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51017 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++++++++ gradlew.bat | 90 +++++ license.txt | 25 ++ plugins/BasicListStatisticsPlugin.groovy | 97 +++++ plugins/OwnStatisticsPlugin.groovy | 139 +++++++ settings.gradle | 2 + .../application/ProgressEvent.java | 69 ++++ .../application/ProgressListener.java | 33 ++ .../application/SessionContext.java | 88 +++++ .../application/SessionContextListener.java | 33 ++ .../java/de/geofroggerfx/dao/CacheDAO.java | 45 +++ .../de/geofroggerfx/dao/jdbc/DataConfig.java | 61 +++ .../de/geofroggerfx/dao/jdbc/JDBCUtils.java | 117 ++++++ .../geofroggerfx/dao/jdbc/JdbcCacheDAO.java | 327 ++++++++++++++++ .../java/de/geofroggerfx/gpx/GPXReader.java | 43 +++ .../gpx/GroundspeakGPXReader.java | 267 +++++++++++++ .../java/de/geofroggerfx/model/Attribute.java | 337 ++++++++++++++++ .../java/de/geofroggerfx/model/Cache.java | 317 ++++++++++++++++ .../de/geofroggerfx/model/CacheListEntry.java | 110 ++++++ .../java/de/geofroggerfx/model/Container.java | 64 ++++ src/main/java/de/geofroggerfx/model/Type.java | 68 ++++ src/main/java/de/geofroggerfx/model/User.java | 63 +++ .../java/de/geofroggerfx/model/Waypoint.java | 166 ++++++++ .../de/geofroggerfx/model/WaypointType.java | 33 ++ .../java/de/geofroggerfx/plugins/Plugin.java | 31 ++ .../de/geofroggerfx/service/CacheService.java | 47 +++ .../service/CacheServiceImpl.java | 66 ++++ .../geofroggerfx/service/PluginService.java | 19 + .../service/PluginServiceImpl.java | 79 ++++ .../de/geofroggerfx/ui/FXMLController.java | 70 ++++ .../java/de/geofroggerfx/ui/GeoFroggerFX.java | 98 +++++ .../ui/GeoFroggerFXController.java | 283 ++++++++++++++ .../de/geofroggerfx/ui/GeocachingIcons.java | 334 ++++++++++++++++ .../ui/details/DetailsController.java | 158 ++++++++ .../geofroggerfx/ui/glyphs/ElusiveIcon.java | 45 +++ .../geofroggerfx/ui/glyphs/ElusiveIcons.java | 359 ++++++++++++++++++ .../ui/glyphs/GeofroggerGlyphsDude.java | 14 + .../geofroggerfx/ui/list/CacheListCell.java | 148 ++++++++ .../geofroggerfx/ui/list/ListController.java | 96 +++++ .../geofroggerfx/dao/jdbc/database.properties | 3 + .../de/geofroggerfx/dao/jdbc/updates/1.sql | 40 ++ .../de/geofroggerfx/ui/application.properties | 10 + .../de/geofroggerfx/ui/details/details.fxml | 140 +++++++ .../geofroggerfx/ui/geofrogger_de.properties | 52 +++ .../geofroggerfx/ui/geofrogger_en.properties | 53 +++ .../de/geofroggerfx/ui/geofroggerfx.css | 4 + .../de/geofroggerfx/ui/geofroggerfx.fxml | 71 ++++ .../de/geofroggerfx/ui/list/list.fxml | 34 ++ .../resources/fonts/elusive/Elusive-Icons.ttf | Bin 0 -> 61640 bytes src/main/resources/fonts/elusive/license.txt | 86 +++++ src/main/resources/fonts/elusive/readme.txt | 10 + .../sofia/SIL OFL Font License Sofia.txt | 94 +++++ .../resources/fonts/sofia/Sofia-Regular.otf | Bin 0 -> 23768 bytes 58 files changed, 5327 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 license.txt create mode 100644 plugins/BasicListStatisticsPlugin.groovy create mode 100644 plugins/OwnStatisticsPlugin.groovy create mode 100644 settings.gradle create mode 100644 src/main/java/de/geofroggerfx/application/ProgressEvent.java create mode 100644 src/main/java/de/geofroggerfx/application/ProgressListener.java create mode 100644 src/main/java/de/geofroggerfx/application/SessionContext.java create mode 100644 src/main/java/de/geofroggerfx/application/SessionContextListener.java create mode 100644 src/main/java/de/geofroggerfx/dao/CacheDAO.java create mode 100644 src/main/java/de/geofroggerfx/dao/jdbc/DataConfig.java create mode 100644 src/main/java/de/geofroggerfx/dao/jdbc/JDBCUtils.java create mode 100644 src/main/java/de/geofroggerfx/dao/jdbc/JdbcCacheDAO.java create mode 100644 src/main/java/de/geofroggerfx/gpx/GPXReader.java create mode 100644 src/main/java/de/geofroggerfx/gpx/GroundspeakGPXReader.java create mode 100644 src/main/java/de/geofroggerfx/model/Attribute.java create mode 100644 src/main/java/de/geofroggerfx/model/Cache.java create mode 100644 src/main/java/de/geofroggerfx/model/CacheListEntry.java create mode 100644 src/main/java/de/geofroggerfx/model/Container.java create mode 100644 src/main/java/de/geofroggerfx/model/Type.java create mode 100644 src/main/java/de/geofroggerfx/model/User.java create mode 100644 src/main/java/de/geofroggerfx/model/Waypoint.java create mode 100644 src/main/java/de/geofroggerfx/model/WaypointType.java create mode 100644 src/main/java/de/geofroggerfx/plugins/Plugin.java create mode 100644 src/main/java/de/geofroggerfx/service/CacheService.java create mode 100644 src/main/java/de/geofroggerfx/service/CacheServiceImpl.java create mode 100644 src/main/java/de/geofroggerfx/service/PluginService.java create mode 100644 src/main/java/de/geofroggerfx/service/PluginServiceImpl.java create mode 100644 src/main/java/de/geofroggerfx/ui/FXMLController.java create mode 100644 src/main/java/de/geofroggerfx/ui/GeoFroggerFX.java create mode 100644 src/main/java/de/geofroggerfx/ui/GeoFroggerFXController.java create mode 100644 src/main/java/de/geofroggerfx/ui/GeocachingIcons.java create mode 100644 src/main/java/de/geofroggerfx/ui/details/DetailsController.java create mode 100644 src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcon.java create mode 100644 src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcons.java create mode 100644 src/main/java/de/geofroggerfx/ui/glyphs/GeofroggerGlyphsDude.java create mode 100644 src/main/java/de/geofroggerfx/ui/list/CacheListCell.java create mode 100644 src/main/java/de/geofroggerfx/ui/list/ListController.java create mode 100644 src/main/resources/de/geofroggerfx/dao/jdbc/database.properties create mode 100644 src/main/resources/de/geofroggerfx/dao/jdbc/updates/1.sql create mode 100644 src/main/resources/de/geofroggerfx/ui/application.properties create mode 100644 src/main/resources/de/geofroggerfx/ui/details/details.fxml create mode 100644 src/main/resources/de/geofroggerfx/ui/geofrogger_de.properties create mode 100644 src/main/resources/de/geofroggerfx/ui/geofrogger_en.properties create mode 100644 src/main/resources/de/geofroggerfx/ui/geofroggerfx.css create mode 100644 src/main/resources/de/geofroggerfx/ui/geofroggerfx.fxml create mode 100644 src/main/resources/de/geofroggerfx/ui/list/list.fxml create mode 100644 src/main/resources/fonts/elusive/Elusive-Icons.ttf create mode 100644 src/main/resources/fonts/elusive/license.txt create mode 100644 src/main/resources/fonts/elusive/readme.txt create mode 100644 src/main/resources/fonts/sofia/SIL OFL Font License Sofia.txt create mode 100644 src/main/resources/fonts/sofia/Sofia-Regular.otf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd2d5e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,76 @@ +# Created by .ignore support plugin (hsz.mobi) +### Java template +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Gradle template +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a1045a4 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +GeoFroggerFX +============ +GeoFroggerFX is a JavaFX desktop application to manage geocaching gpx files. +Webpage of the project: [http://www.geofroggerfx.de](http://www.geofroggerfx.de) + +This is the second try after a complete rewrite. + +Requirements +------------ +- JDK8 + +Building and run with Gradle +---------------------------- +* Set your environment variable JAVA_HOME to the directory that contains the JDK1.8 +* gradlew run + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..b5512b9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'java' +apply plugin: 'groovy' +apply plugin:'application' +apply plugin: 'idea' + +sourceCompatibility = 1.8 +version = '1.0' +mainClassName = 'de.geofroggerfx.ui.GeoFroggerFX' + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.11' + compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.0' + compile group: 'org.springframework', name:'spring-context', version: '4.1.5.RELEASE' + compile group: 'org.springframework', name:'spring-jdbc', version: '4.1.5.RELEASE' + compile group: 'org.codehaus.woodstox', name: 'woodstox-core-asl', version: '4.4.1' + compile group: 'org.codehaus.woodstox', name: 'stax2-api', version: '4.0.0' + compile group: 'com.h2database', name: 'h2', version: '1.4.186' + compile group: 'de.jensd', name: 'fontawesomefx', version: '8.2' + compile group: 'org.controlsfx', name: 'controlsfx', version: '8.20.8' + compile group: 'com.lynden', name: 'GMapsFX', version: '1.1.1' +} + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..3d0dee6e8edfecc92e04653ec780de06f7b34f8b GIT binary patch literal 51017 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 H-~Rf4?cF@p literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d4e1165 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 09 19:47:19 CET 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..e615d2e --- /dev/null +++ b/license.txt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013-2015 Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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. + */ \ No newline at end of file diff --git a/plugins/BasicListStatisticsPlugin.groovy b/plugins/BasicListStatisticsPlugin.groovy new file mode 100644 index 0000000..546e27e --- /dev/null +++ b/plugins/BasicListStatisticsPlugin.groovy @@ -0,0 +1,97 @@ +import de.geofroggerfx.plugins.Plugin +import javafx.collections.FXCollections +import javafx.scene.chart.PieChart +import javafx.scene.control.ButtonType +import javafx.scene.control.Dialog +import javafx.scene.control.ScrollPane +import javafx.scene.layout.VBox + +import static javafx.scene.chart.PieChart.Data + +class BasicListStatisticsPlugin implements Plugin { + + private difficultyTerrainValues = ['1', '1.5', '2', '2.5', '3', '3.5', '4', '4.5', '5'] + + final String name = "Basic List Statistic" + final String version = "0.0.2" + + @Override + void run(final Map context) { + showDialog(createContent(context.cacheService)) + } + + /** + * creates the statistic charts to show + * @param sessionContext context with the cache list in it + * @return + */ + private javafx.scene.Node createContent(cacheService) { + + // get the cache list from service + def cacheList = cacheService.getAllCaches() + + // create a vbox as layout container + VBox contenPane = new VBox() + + // groovy maps for selecting the statistic numbers + def typeStats = [:] + def difficultyStats = [:] + def terrainStats = [:] + + // iterate over all the caches and count the data + for (def cache in cacheList) { + incrementStats(typeStats, cache.type) + incrementStats(difficultyStats, cache.difficulty) + incrementStats(terrainStats, cache.terrain) + } + + // create javafx chart + def typeData = FXCollections.observableArrayList() + typeStats.each() { key, value -> typeData.add(new Data(key.toString() + ' (' + value + ')', value as double)) } + def typeChart = new PieChart(typeData); + typeChart.setTitle("Spreading of cache types in list."); + + // create javafx chart + def difficultyData = FXCollections.observableArrayList() + difficultyTerrainValues.each { + def value = difficultyStats[it] + if (value) difficultyData.add(new Data(it + ' (' + value + ')', value)) + } + def difficultyChart = new PieChart(difficultyData); + difficultyChart.setTitle("Spreading of difficulties in list."); + + // create javafx chart + def terrainData = FXCollections.observableArrayList() + difficultyTerrainValues.each { + def value = terrainStats[it] + if (value) terrainData.add(new Data(it + ' (' + value + ')', value)) + } + def terrainChart = new PieChart(terrainData); + terrainChart.setTitle("Spreading of terrain in list."); + + // add charts to layout container + contenPane.children.addAll(typeChart, difficultyChart, terrainChart) + + // return the layout container + def scrollPane = new ScrollPane(contenPane) + scrollPane.minWidth = 600 + scrollPane.minHeight = 450 + scrollPane + } + + private void incrementStats(map, key) { + map[key] = map[key] ? map[key] + 1 : 1 + } + + private void showDialog(content) { + Dialog dialog = new Dialog() + dialog.resizableProperty().set(true) + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.CLOSE); + dialog.setTitle(name + " (" + version + ")") + dialog.setHeaderText("This example plugin shows some stats based on the current list.\nThese statistics are global statistics over all caches in list\nand not personalized statistics over own founds.") + dialog.getDialogPane().setContent(content) + dialog.setWidth(800) + dialog.setHeight(600) + dialog.show() + } +} \ No newline at end of file diff --git a/plugins/OwnStatisticsPlugin.groovy b/plugins/OwnStatisticsPlugin.groovy new file mode 100644 index 0000000..762e8ec --- /dev/null +++ b/plugins/OwnStatisticsPlugin.groovy @@ -0,0 +1,139 @@ +import de.geofroggerfx.plugins.Plugin +import javafx.collections.FXCollections +import javafx.concurrent.Service +import javafx.concurrent.Task +import javafx.concurrent.WorkerStateEvent +import javafx.event.EventHandler +import javafx.scene.chart.PieChart +import javafx.scene.control.ButtonType +import javafx.scene.control.Dialog +import javafx.scene.control.ScrollPane +import javafx.scene.layout.Priority +import javafx.scene.layout.VBox + +import static javafx.scene.chart.PieChart.Data + +class OwnStatisticsPlugin implements Plugin { + + private difficultyTerrainValues = ['1', '1.5', '2', '2.5', '3', '3.5', '4', '4.5', '5'] + + final String name = "Own Statistic" + final String version = "0.0.1" + final CalculateService service = new CalculateService(); + + OwnStatisticsPlugin() { + service.onSucceeded = { + showDialog(createContent(it.source.value)) + } as EventHandler; + } + + @Override + void run(final Map context) { + calculateStats(context.cacheService) + } + +/** + * creates the statistic charts to show + * @param sessionContext context with the cache list in it + * @return + */ + private void calculateStats(cacheService) { + // get the cache list from service + def cacheList = cacheService.getAllCaches() + service.cacheList = cacheList; + service.restart(); + } + + /** + * creates the statistic charts to show + * @param sessionContext context with the cache list in it + * @return + */ + private javafx.scene.Node createContent(result) { + + // create a vbox as layout container + VBox contenPane = new VBox() + contenPane.prefWidth = 600 + VBox.setVgrow(contenPane, Priority.ALWAYS); + + // create javafx chart + def typeData = FXCollections.observableArrayList() + result.typeStats.each() { key, value -> typeData.add(new Data(key.toString() + ' (' + value + ')', value as double)) } + def typeChart = new PieChart(typeData); + typeChart.setTitle("Spreading of cache types found (${result.foundCount})."); + + // create javafx chart + def difficultyData = FXCollections.observableArrayList() + difficultyTerrainValues.each { + def value = result.difficultyStats[it] + if (value) difficultyData.add(new Data(it + ' (' + value + ')', value)) + } + def difficultyChart = new PieChart(difficultyData); + difficultyChart.setTitle("Spreading of difficulties found (${result.foundCount})."); + + // create javafx chart + def terrainData = FXCollections.observableArrayList() + difficultyTerrainValues.each { + def value = result.terrainStats[it] + if (value) terrainData.add(new Data(it + ' (' + value + ')', value)) + } + def terrainChart = new PieChart(terrainData); + terrainChart.setTitle("Spreading of terrain found (${result.foundCount})."); + + // add charts to layout container + contenPane.children.addAll(typeChart, difficultyChart, terrainChart) + + // return the layout container + def scrollPane = new ScrollPane(contenPane) + scrollPane.minWidth = 640 + scrollPane.minHeight = 450 + scrollPane + } + + private void showDialog(content) { + Dialog dialog = new Dialog() + dialog.resizableProperty().set(true) + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.CLOSE); + dialog.setTitle(name + " (" + version + ")") + dialog.setHeaderText("This example plugin shows some stats based on the current list.\nThese statistics are found statistics, based on all found caches in the list.") + dialog.getDialogPane().setContent(content) + dialog.setWidth(800) + dialog.setHeight(600) + dialog.show() + } +} + +class CalculateService extends Service { + + def cacheList + + @Override + protected Task createTask() { + return new Task() { + @Override + protected LinkedHashMap call() throws Exception { + + def typeStats = [:] + def difficultyStats = [:] + def terrainStats = [:] + + def foundCount = 0 + + for (def cache in cacheList) { + if (cache.found) { + foundCount++ + incrementStats(typeStats, cache.type) + incrementStats(difficultyStats, cache.difficulty) + incrementStats(terrainStats, cache.terrain) + } + } + ['foundCount': foundCount, 'typeStats': typeStats, 'difficultyStats': difficultyStats, 'terrainStats': terrainStats] + } + }; + } + + private void incrementStats(map, key) { + map[key] = map[key] ? map[key] + 1 : 1 + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..874c222 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'GeoFroggerFX2' + diff --git a/src/main/java/de/geofroggerfx/application/ProgressEvent.java b/src/main/java/de/geofroggerfx/application/ProgressEvent.java new file mode 100644 index 0000000..e7a84a6 --- /dev/null +++ b/src/main/java/de/geofroggerfx/application/ProgressEvent.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.application; + +/** + * @author Andreas + */ +public class ProgressEvent { + + public enum State {INITIALIZED, STARTED, RUNNING, FINISHED} + + ; + public static final double INDETERMINATE_PROGRESS = -1.0; + + private final String module; + private final State state; + private final String message; + private final Double progress; + + public ProgressEvent(String module, State state, String message) { + this(module, state, message, INDETERMINATE_PROGRESS); + } + + public ProgressEvent(String module, State state, String message, Double progress) { + this.module = module; + this.state = state; + this.message = message; + this.progress = progress; + } + + public String getModule() { + return module; + } + + public State getState() { + return state; + } + + public String getMessage() { + return message; + } + + public Double getProgress() { + return progress; + } +} diff --git a/src/main/java/de/geofroggerfx/application/ProgressListener.java b/src/main/java/de/geofroggerfx/application/ProgressListener.java new file mode 100644 index 0000000..f9d658a --- /dev/null +++ b/src/main/java/de/geofroggerfx/application/ProgressListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.application; + +/** + * @author Andreas + */ +public interface ProgressListener { + void progress(ProgressEvent event); +} diff --git a/src/main/java/de/geofroggerfx/application/SessionContext.java b/src/main/java/de/geofroggerfx/application/SessionContext.java new file mode 100644 index 0000000..395230a --- /dev/null +++ b/src/main/java/de/geofroggerfx/application/SessionContext.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.application; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Andreas + */ +@Component +public class SessionContext { + + public static final String CACHE_ENTRY_LIST = "CACHE_ENTRY_LIST"; + public static final String CURRENT_CACHE = "CURRENT_CACHE"; + + private final Map map = new ConcurrentHashMap<>(); + private final Map> sessionListeners = new HashMap<>(); + + public void setData(String key, Object value) { + if (value == null && map.containsKey(key)) { + map.remove(key); + } else if (value != null) { + map.put(key, value); + } + fireSessionEvent(key); + } + + public Object getData(String key) { + if (map.containsKey(key)) { + return map.get(key); + } else { + return null; + } + } + + public void addListener(String key, SessionContextListener sessionListener) { + + List listenerList; + if (sessionListeners.containsKey(key)) { + listenerList = sessionListeners.get(key); + } else { + listenerList = new ArrayList<>(); + sessionListeners.put(key, listenerList); + } + + if (!listenerList.contains(sessionListener)) { + listenerList.add(sessionListener); + } + } + + private void fireSessionEvent(String key) { + if (sessionListeners.containsKey(key)) { + sessionListeners. + get(key). + stream(). + forEach(SessionContextListener::sessionContextChanged); + } + } +} diff --git a/src/main/java/de/geofroggerfx/application/SessionContextListener.java b/src/main/java/de/geofroggerfx/application/SessionContextListener.java new file mode 100644 index 0000000..d071c60 --- /dev/null +++ b/src/main/java/de/geofroggerfx/application/SessionContextListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.application; + +/** + * @author Andreas + */ +public interface SessionContextListener { + void sessionContextChanged(); +} diff --git a/src/main/java/de/geofroggerfx/dao/CacheDAO.java b/src/main/java/de/geofroggerfx/dao/CacheDAO.java new file mode 100644 index 0000000..43e35f8 --- /dev/null +++ b/src/main/java/de/geofroggerfx/dao/CacheDAO.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.dao; + +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.model.CacheListEntry; + +import java.util.List; + +/** + * Created by Andreas on 11.03.2015. + */ +public interface CacheDAO { + + void save(List listOfCaches); + void update(Cache cache); + List getAllCachEntriesSortBy(String name, String asc); + + Cache getCacheForId(long id); + + List getAllCaches(); +} diff --git a/src/main/java/de/geofroggerfx/dao/jdbc/DataConfig.java b/src/main/java/de/geofroggerfx/dao/jdbc/DataConfig.java new file mode 100644 index 0000000..9fd26aa --- /dev/null +++ b/src/main/java/de/geofroggerfx/dao/jdbc/DataConfig.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.dao.jdbc; + +import org.h2.jdbcx.JdbcDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; + +/** + * Created by Andreas on 11.03.2015. + */ +@Configuration +@PropertySource("classpath:/de/geofroggerfx/dao/jdbc/database.properties") +public class DataConfig { + @Autowired + private Environment env; + + @Bean + public DataSource dataSource(){ + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL(env.getProperty("jdbc.url")); + dataSource.setUser(env.getProperty("jdbc.username")); + dataSource.setPassword(env.getProperty("jdbc.password")); + return dataSource; + } + + @Bean + public PlatformTransactionManager transactionManager(){ + return new DataSourceTransactionManager(dataSource()); + } +} diff --git a/src/main/java/de/geofroggerfx/dao/jdbc/JDBCUtils.java b/src/main/java/de/geofroggerfx/dao/jdbc/JDBCUtils.java new file mode 100644 index 0000000..a83b46f --- /dev/null +++ b/src/main/java/de/geofroggerfx/dao/jdbc/JDBCUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.dao.jdbc; + +/** + * Created by Andreas on 11.03.2015. + */ +public class JDBCUtils { + + public static String generateSelectSQL(String table, String... columns) { + + assert columns != null && columns.length > 0 : "columns must not be null or empty"; + + StringBuffer sql = new StringBuffer(); + sql.append("SELECT "); + + for (int columnCounter = 0; columnCounter < columns.length-1; columnCounter++) { + addCommaToValue(sql, columns[columnCounter]); + } + sql.append(columns[columns.length-1]); + sql.append(" FROM ").append(table); + return sql.toString(); + } + + public static String generateUpdateSQL(String table, String... columns) { + + assert columns != null && columns.length > 0 : "columns must not be null or empty"; + + StringBuffer sql = new StringBuffer(); + sql.append("UPDATE ").append(table).append(" SET "); + + for (int columnCounter = 0; columnCounter < columns.length-1; columnCounter++) { + addCommaToValue(sql, columns[columnCounter]+"=? "); + } + sql.append(columns[columns.length-1]+"=? "); + sql.append(" WHERE ID = ?"); + return sql.toString(); + } + + public static String generateInsertSQL(String table, String... columns) { + + assert columns != null && columns.length > 0 : "columns must not be null or empty"; + + StringBuffer sql = new StringBuffer(); + sql.append("INSERT INTO ").append(table).append("("); + + for (int columnCounter = 0; columnCounter < columns.length-1; columnCounter++) { + addCommaToValue(sql, columns[columnCounter]); + } + sql.append(columns[columns.length-1]); + + sql.append(") "); + sql.append("values("); + + for (int columnCounter = 0; columnCounter < columns.length-1; columnCounter++) { + addCommaToValue(sql, "?"); + } + sql.append("?"); + sql.append(") "); + + return sql.toString(); + } + + public static String generateInsertSQL(String table, String[] columns, Object[] values) { + + assert columns != null && columns.length > 0 : "columns must not be null or empty"; + + StringBuffer sql = new StringBuffer(); + sql.append("INSERT INTO ").append(table).append("("); + + for (int columnCounter = 0; columnCounter < columns.length-1; columnCounter++) { + addCommaToValue(sql, columns[columnCounter]); + } + sql.append(columns[columns.length-1]); + + sql.append(") "); + sql.append("values("); + + for (int valuesCounter = 0; valuesCounter < values.length-1; valuesCounter++) { + addCommaToValue(sql, "'"+values[valuesCounter]+"'"); + } + sql.append("'"+values[values.length-1]+"'"); + sql.append(") "); + + return sql.toString(); + } + + + public static void addCommaToValue(StringBuffer sql, String value) { + sql.append(value); + sql.append(","); + } + +} diff --git a/src/main/java/de/geofroggerfx/dao/jdbc/JdbcCacheDAO.java b/src/main/java/de/geofroggerfx/dao/jdbc/JdbcCacheDAO.java new file mode 100644 index 0000000..ddc785b --- /dev/null +++ b/src/main/java/de/geofroggerfx/dao/jdbc/JdbcCacheDAO.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.dao.jdbc; + +import de.geofroggerfx.dao.CacheDAO; +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.model.CacheListEntry; +import de.geofroggerfx.model.Waypoint; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static de.geofroggerfx.dao.jdbc.JDBCUtils.*; +import static de.geofroggerfx.model.Container.groundspeakStringToContainer; +import static de.geofroggerfx.model.Type.groundspeakStringToType; + +/** + * Created by Andreas on 11.03.2015. + */ +@Repository +public class JdbcCacheDAO implements CacheDAO { + + private static final Logger LOGGER = Logger.getLogger("JdbcCacheDAO"); + private static final String CACHE_TABLE = "cache"; + private static final String WAYPOINT_TABLE = "waypoint"; + + + private String[] columnListCache = new String[] { + "id", + "name", + "placed_by", + "available", + "archived", + "found", + "difficulty", + "terrain", + "country", + "state", + "short_description", + "short_description_html", + "long_description", + "long_description_html", + "encoded_hints", + "container", + "type", + "user_id"}; + + private String[] columnListWaypoint = new String[] { + "name", + "cache_id", + "latitude", + "longitude", + "time", + "description", + "url", + "urlName", + "symbol", + "type"}; + + + private JdbcTemplate jdbcTemplate; + + @Autowired + public void setDataConfig(DataConfig dataConfig) { + this.jdbcTemplate = new JdbcTemplate(dataConfig.dataSource()); + } + + @Override + public void save(List listOfCaches) { + deleteExistingCaches(listOfCaches); + batchInsertCaches(listOfCaches); + batchInsertWaypoints(listOfCaches); + } + + @Override + public void update(Cache cache) { + String updateSQL = generateUpdateSQL(CACHE_TABLE, columnListCache); + jdbcTemplate.update(updateSQL, valuesFromCache(cache, cache.getId())); + updateSQL = generateUpdateSQL(WAYPOINT_TABLE, columnListWaypoint); + jdbcTemplate.update(updateSQL, valuesFromWaypoint(cache, cache.getMainWayPoint().getName())); + } + + @Override + public List getAllCachEntriesSortBy(String name, String asc) { + return this.jdbcTemplate.query( + "SELECT c.id, c.name AS name, c.name AS code, c.difficulty, c.terrain, c.type FROM "+CACHE_TABLE+" c ORDER BY "+name+" "+asc, + (rs, rowNum) -> { + return new CacheListEntry( + rs.getLong("id"), + rs.getString("name"), + rs.getString("code"), + rs.getString("difficulty"), + rs.getString("terrain"), + groundspeakStringToType(rs.getString("type"))); + }); + } + + @Override + public Cache getCacheForId(long id) { + return this.jdbcTemplate.queryForObject( + generateSelectSQL(CACHE_TABLE, columnListCache) + " WHERE id=?", + new Object[]{id}, + (rs, rowNum) -> { + Cache cache = getCacheFromResultSet(rs); + Waypoint waypoint = getWaypointForCacheId(cache.getId()); + cache.setMainWayPoint(waypoint); + waypoint.setCache(cache); + return cache; + }); + } + + @Override + public List getAllCaches() { + return this.jdbcTemplate.query( + generateSelectSQL(CACHE_TABLE, columnListCache), + (rs, rowNum) -> { + return getCacheFromResultSet(rs); + }); + + } + + private void batchInsertCaches(List listOfCaches) { + LOGGER.info("try to save " + listOfCaches.size() + " caches"); + long startInsert = System.currentTimeMillis(); + + List batch = listOfCaches.parallelStream().map(this::valuesFromCache).collect(Collectors.toList()); + + try { + String insertSQL = generateInsertSQL(CACHE_TABLE, columnListCache); + int[] updateCounts = jdbcTemplate.batchUpdate( + insertSQL, + batch); + int updatedRows = 0; + for (int count:updateCounts) { + updatedRows += count; + } + LOGGER.info("batch inserted "+updatedRows+" caches"); + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + + long endInsert = System.currentTimeMillis(); + LOGGER.info("try to save " + listOfCaches.size() + " caches -> done in " + (endInsert - startInsert) + " ms"); + } + + private void deleteExistingCaches(List listOfCaches) { + LOGGER.info("try to delete existing caches"); + long startDelete = System.currentTimeMillis(); + StringBuffer sb = new StringBuffer(); + for (int current = 0; current < listOfCaches.size()-1; current++) { + addCommaToValue(sb, Long.toString(listOfCaches.get(current).getId())); + } + sb.append(listOfCaches.get(listOfCaches.size()-1).getId()); + + try { + int result = jdbcTemplate.update("DELETE FROM "+CACHE_TABLE+" WHERE id IN ("+sb.toString()+")"); + LOGGER.info("deleted "+result+" caches"); + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + long endDelete = System.currentTimeMillis(); + LOGGER.info("try to delete existing caches -> done in " + (endDelete - startDelete) + " ms"); + } + + + private void batchInsertWaypoints(List listOfCaches) { + LOGGER.info("try to save "+listOfCaches.size()+" waypoints"); + long startInsert = System.currentTimeMillis(); + + List batch = listOfCaches.parallelStream().map(this::valuesFromWaypoint).collect(Collectors.toList()); + + try { + String insertSQL = generateInsertSQL(WAYPOINT_TABLE, columnListWaypoint); + int[] updateCounts = jdbcTemplate.batchUpdate( + insertSQL, + batch); + int updatedRows = 0; + for (int count:updateCounts) { + updatedRows += count; + } + LOGGER.info("batch inserted "+updatedRows+" waypoints"); + } catch (Throwable e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + + long endInsert = System.currentTimeMillis(); + LOGGER.info("try to save "+listOfCaches.size()+" waypoints -> done in "+(endInsert-startInsert)+" ms"); + } + + private Waypoint getWaypointForCacheId(long cacheId) { + return this.jdbcTemplate.queryForObject( + generateSelectSQL(WAYPOINT_TABLE, columnListWaypoint) + " WHERE cache_id=?", + new Object[]{cacheId}, + (rs, rowNum) -> { + return getWaypointFromResultSet(rs); + }); + + } + + + private Cache getCacheFromResultSet(ResultSet rs) throws SQLException { + Cache cache = new Cache(); + cache.setId(rs.getLong("id")); + cache.setName(rs.getString("name")); + cache.setDifficulty(rs.getString("difficulty")); + cache.setTerrain(rs.getString("terrain")); + cache.setPlacedBy(rs.getString("placed_by")); + cache.setAvailable(rs.getBoolean("available")); + cache.setArchived(rs.getBoolean("archived")); + cache.setFound(rs.getBoolean("found")); + cache.setCountry(rs.getString("country")); + cache.setState(rs.getString("state")); + cache.setShortDescription(rs.getString("short_description")); + cache.setLongDescription(rs.getString("long_description")); + cache.setEncodedHints(rs.getString("encoded_hints")); + cache.setShortDescriptionHtml(rs.getBoolean("short_description_html")); + cache.setLongDescriptionHtml(rs.getBoolean("long_description_html")); + cache.setContainer(groundspeakStringToContainer(rs.getString("container"))); + cache.setType(groundspeakStringToType(rs.getString("type"))); + cache.setOwner(null); + return cache; + } + + private Object[] valuesFromCache(Cache cache) { + return new Object[]{ + cache.getId(), + cache.getName(), + cache.getPlacedBy(), + cache.getAvailable(), + cache.getArchived(), + cache.getFound(), + cache.getDifficulty(), + cache.getTerrain(), + cache.getCountry(), + cache.getState(), + cache.getShortDescription(), + cache.getShortDescriptionHtml(), + cache.getLongDescription(), + cache.getLongDescriptionHtml(), + cache.getEncodedHints(), + cache.getContainer().toGroundspeakString(), + cache.getType().toGroundspeakString(), + cache.getOwner() != null ? cache.getOwner().getId() : null }; + } + + private Waypoint getWaypointFromResultSet(ResultSet rs) throws SQLException { + Waypoint waypoint = new Waypoint(); + waypoint.setName(rs.getString("name")); + waypoint.setLatitude(rs.getDouble("latitude")); + waypoint.setLongitude(rs.getDouble("longitude")); + waypoint.setTime(rs.getTimestamp("time")); + waypoint.setDescription(rs.getString("description")); + waypoint.setUrl(rs.getString("url")); + waypoint.setUrlName(rs.getString("urlName")); + waypoint.setSymbol(rs.getString("symbol")); +// waypoint.setType(WaypointType.rs.getString("description")); + return waypoint; + } + + private Object[] valuesFromWaypoint(Cache cache) { + return new Object[]{ + cache.getMainWayPoint().getName(), + cache.getId(), + cache.getMainWayPoint().getLatitude(), + cache.getMainWayPoint().getLongitude(), + cache.getMainWayPoint().getTime(), + cache.getMainWayPoint().getDescription(), + cache.getMainWayPoint().getUrl(), + cache.getMainWayPoint().getUrlName(), + cache.getMainWayPoint().getSymbol(), + null + }; + /* + "name", + "cache_id", + "latitude", + "longitude", + "time", + "description", + "url", + "urlName", + "symbol", + "type" + */ + } + + + private Object[] valuesFromCache(Cache cache, long id) { + return Arrays.asList(valuesFromCache(cache), id).toArray(); + } + + private Object[] valuesFromWaypoint(Cache cache, String name) { + return Arrays.asList(valuesFromWaypoint(cache), name).toArray(); + } + +} diff --git a/src/main/java/de/geofroggerfx/gpx/GPXReader.java b/src/main/java/de/geofroggerfx/gpx/GPXReader.java new file mode 100644 index 0000000..85fed04 --- /dev/null +++ b/src/main/java/de/geofroggerfx/gpx/GPXReader.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.gpx; + +import de.geofroggerfx.application.ProgressListener; +import de.geofroggerfx.model.Cache; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * TODO: describe the class + * + * @author Andreas Billmann + */ +public interface GPXReader { + List load(File fileName) throws IOException; + void addListener(ProgressListener listener); +} diff --git a/src/main/java/de/geofroggerfx/gpx/GroundspeakGPXReader.java b/src/main/java/de/geofroggerfx/gpx/GroundspeakGPXReader.java new file mode 100644 index 0000000..b589449 --- /dev/null +++ b/src/main/java/de/geofroggerfx/gpx/GroundspeakGPXReader.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.gpx; + +import de.geofroggerfx.application.ProgressEvent; +import de.geofroggerfx.application.ProgressListener; +import de.geofroggerfx.model.*; +import org.springframework.stereotype.Service; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +/** + * @author Andreas Billmann + */ +@Service +public class GroundspeakGPXReader implements GPXReader { + + private static final Logger LOGGER = Logger.getLogger(GroundspeakGPXReader.class.getName()); + + public static final String LAT = "lat"; + public static final String LON = "lon"; + public static final String TIME = "time"; + public static final String NAME = "name"; + public static final String DESC = "desc"; + public static final String WPT = "wpt"; + public static final String URL = "url"; + public static final String URLNAME = "urlname"; + public static final String SYM = "sym"; + public static final String TYPE = "type"; + public static final String CACHE = "cache"; + public static final String GROUNDSPEAK = "groundspeak"; + public static final String ID = "id"; + public static final String AVAILABLE = "available"; + public static final String ARCHIVED = "archived"; + public static final String PLACED_BY = "placed_by"; + public static final String OWNER = "owner"; + public static final String CONTAINER = "container"; + public static final String ATTRIBUTES = "attributes"; + public static final String ATTRIBUTE = "attribute"; + public static final String INC = "inc"; + public static final String DIFFICULTY = "difficulty"; + public static final String TERRAIN = "terrain"; + public static final String COUNTRY = "country"; + public static final String STATE = "state"; + public static final String SHORT_DESCRIPTION = "short_description"; + public static final String HTML = "html"; + public static final String LONG_DESCRIPTION = "long_description"; + public static final String ENCODED_HINTS = "encoded_hints"; + public static final String LOGS = "logs"; + public static final String LOG = "log"; + public static final String DATE = "date"; + public static final String FINDER = "finder"; + public static final String ENCODED = "encoded"; + public static final String TRAVELBUGS = "travelbugs"; + public static final String TRAVELBUG = "travelbug"; + public static final String TEXT = "text"; + public static final String REF = "ref"; + + // 2011-12-03T10:15:30+01:00 + private final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + + private final List listeners = new ArrayList<>(); + + private final Map userCache = new HashMap<>(); + + + @Override + public void addListener(ProgressListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + @Override + public List load(File file) throws IOException { + fireEvent(new ProgressEvent("GPX Reader", + ProgressEvent.State.STARTED, + "Load File " + file.getName() + " started.")); + List modelList = new ArrayList<>(); + + long start = System.currentTimeMillis(); + XMLInputFactory factory = XMLInputFactory.newInstance(); + try (FileInputStream fis = new FileInputStream(file)) { + XMLStreamReader reader = factory.createXMLStreamReader(fis); + + Waypoint waypoint = null; + Cache cache = null; + String currentLevel = null; + String startTag; + StringBuffer tagContent = new StringBuffer(); + + while(reader.hasNext()) { + int event = reader.next(); + + switch (event) { + case XMLStreamConstants.START_ELEMENT: + + tagContent = new StringBuffer(); + startTag = reader.getLocalName(); + switch (startTag) { + case WPT: + currentLevel = WPT; + waypoint = new Waypoint(); + waypoint.setLatitude(Double.parseDouble(reader.getAttributeValue(0))); + waypoint.setLongitude(Double.parseDouble(reader.getAttributeValue(1))); + break; + case CACHE: + currentLevel = CACHE; + cache = new Cache(); + cache.setMainWayPoint(waypoint); + waypoint.setCache(cache); + cache.setId(Long.parseLong(reader.getAttributeValue(0))); + cache.setAvailable(Boolean.parseBoolean(reader.getAttributeValue(1))); + cache.setArchived(Boolean.parseBoolean(reader.getAttributeValue(2))); + modelList.add(cache); + break; + case LOG: + currentLevel = LOG; + break; + case ATTRIBUTE: + cache.getAttributes().add( + Attribute.groundspeakAttributeToAttribute( + Long.parseLong(reader.getAttributeValue(0)), + Boolean.parseBoolean(reader.getAttributeValue(1)))); + break; + case SHORT_DESCRIPTION: + cache.setShortDescriptionHtml(Boolean.parseBoolean(reader.getAttributeValue(0))); + break; + case LONG_DESCRIPTION: + cache.setLongDescriptionHtml(Boolean.parseBoolean(reader.getAttributeValue(0))); + break; + } + + break; + + case XMLStreamConstants.CHARACTERS: + tagContent.append(reader.getText().trim()); + break; + + case XMLStreamConstants.END_ELEMENT: + switch (reader.getLocalName()) { + case WPT: + currentLevel = null; + break; + case CACHE: + cache = null; + currentLevel = WPT; + break; + case LOG: + currentLevel = CACHE; + break; + case NAME: + if (WPT.equals(currentLevel)) { + waypoint.setName(tagContent.toString()); + } else if (CACHE.equals(currentLevel)) { + cache.setName(tagContent.toString()); + } + break; + case DESC: + if (WPT.equals(currentLevel)) { + waypoint.setDescription(tagContent.toString()); + } + break; + case URL: + waypoint.setUrl(tagContent.toString()); + break; + case URLNAME: + waypoint.setUrlName(tagContent.toString()); + break; + case SYM: + waypoint.setSymbol(tagContent.toString()); + break; + case TYPE: + if (CACHE.equals(currentLevel)) { + cache.setType(Type.groundspeakStringToType(tagContent.toString())); + } + break; + case PLACED_BY: + cache.setPlacedBy(tagContent.toString()); + break; + case OWNER: + break; + case CONTAINER: + cache.setContainer(Container.groundspeakStringToContainer(tagContent.toString())); + break; + case ATTRIBUTES: + // do nothing + break; + case DIFFICULTY: + cache.setDifficulty(tagContent.toString()); + break; + case TERRAIN: + cache.setTerrain(tagContent.toString()); + break; + case COUNTRY: + cache.setCountry(tagContent.toString()); + break; + case STATE: + cache.setState(tagContent.toString()); + break; + case SHORT_DESCRIPTION: + cache.setShortDescription(tagContent.toString()); + break; + case LONG_DESCRIPTION: + cache.setLongDescription(tagContent.toString()); + break; + case ENCODED_HINTS: + cache.setEncodedHints(tagContent.toString()); + break; + } + break; + } + } + } catch (Throwable e) { + e.printStackTrace(); + throw new IOException(e.getMessage(), e); + } + + long end = System.currentTimeMillis(); + LOGGER.info(modelList.size()+" Caches geladen in "+(end-start)+"ms"); + + fireEvent(new ProgressEvent("GPX Reader", + ProgressEvent.State.FINISHED, + "Load File " + file.getName() + " finished.")); + return modelList; + } + + private void fireEvent(ProgressEvent event) { + listeners.stream().forEach((l) -> { + l.progress(event); + }); + } +} diff --git a/src/main/java/de/geofroggerfx/model/Attribute.java b/src/main/java/de/geofroggerfx/model/Attribute.java new file mode 100644 index 0000000..0a6f17d --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/Attribute.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andreas Billmann + */ +public enum Attribute { + + // id:1 text:Dogs + DOGS_TRUE(1), + DOGS_FALSE(-1), + + // id:2 text:Access or parking fee + ACCESS_OR_PARKING_FEE_TRUE(2), + ACCESS_OR_PARKING_FEE_FALSE(-2), + + // id:3 text:Climbing gear + CLIMBING_GEAR_TRUE(3), + CLIMBING_GEAR_FALSE(-3), + + // id:4 text:Boat + BOAT_TRUE(4), + BOAT_FALSE(-4), + + // id:5 text:Scuba gear + SCUBA_GEAR_TRUE(5), + SCUBA_GEAR_FALSE(-5), + + // id:6 text:Recommended for kids + RECOMMENDED_FOR_KIDS_TRUE(6), + RECOMMENDED_FOR_KIDS_FALSE(-6), + + // id:7 text:Takes less than an hour + TAKES_LESS_THAN_AN_HOUR_TRUE(7), + TAKES_LESS_THAN_AN_HOUR_FALSE(-7), + + // id:8 text:Scenic view + SCENIC_VIEW_TRUE(8), + SCENIC_VIEW_FALSE(-8), + + // id:9 text:Significant Hike + SIGNIFICANT_HIKE_TRUE(9), + SIGNIFICANT_HIKE_FALSE(-9), + + // id:10 text:Difficult climbing + DIFFICULT_CLIMBING_TRUE(10), + DIFFICULT_CLIMBING_FALSE(-10), + + // id:11 text:May require wading + MAY_REQUIRE_WADING_TRUE(11), + MAY_REQUIRE_WADING_FALSE(-11), + + // id:12 text:May require swimming + MAY_REQUIRE_SWIMMING_TRUE(12), + MAY_REQUIRE_SWIMMING_FALSE(-12), + + // id:13 text:Available at all times + AVAILABLE_AT_ALL_TIMES_TRUE(13), + AVAILABLE_AT_ALL_TIMES_FALSE(-13), + + // id:14 text:Recommended at night + RECOMMENDED_AT_NIGHT_TRUE(14), + RECOMMENDED_AT_NIGHT_FALSE(-14), + + // id:15 text:Available during winter + AVAILABLE_DURING_WINTER_TRUE(15), + AVAILABLE_DURING_WINTER_FALSE(-15), + + // id:17 text:Poison plants + POISON_PLANTS_TRUE(17), + POISON_PLANTS_FALSE(-17), + + // id:18 text:Dangerous Animals + DANGEROUS_ANIMALS_TRUE(18), + DANGEROUS_ANIMALS_FALSE(-18), + + // id:19 text:Ticks + TICKS_TRUE(19), + TICKS_FALSE(-19), + + // id:20 text:Abandoned mines + ABANDONED_MINES_TRUE(20), + ABANDONED_MINES_FALSE(-20), + + // id:21 text:Cliff / falling rocks + CLIFF_FALLING_ROCKS_TRUE(21), + CLIFF_FALLING_ROCKS_FALSE(-21), + + // id:22 text:Hunting + HUNTING_TRUE(22), + HUNTING_FALSE(-22), + + // id:23 text:Dangerous area + DANGEROUS_AREA_TRUE(23), + DANGEROUS_AREA_FALSE(-23), + + // id:24 text:Wheelchair accessible + WHEELCHAIR_ACCESSIBLE_TRUE(24), + WHEELCHAIR_ACCESSIBLE_FALSE(-24), + + // id:25 text:Parking available + PARKING_AVAILABLE_TRUE(25), + PARKING_AVAILABLE_FALSE(-25), + + // id:26 text:Public transportation + PUBLIC_TRANSPORTATION_TRUE(26), + PUBLIC_TRANSPORTATION_FALSE(-26), + + // id:27 text:Drinking water nearby + DRINKING_WATER_NEARBY_TRUE(27), + DRINKING_WATER_NEARBY_FALSE(-27), + + // id:28 text:Public restrooms nearby + PUBLIC_RESTROOMS_NEARBY_TRUE(28), + PUBLIC_RESTROOMS_NEARBY_FALSE(-28), + + // id:29 text:Telephone nearby + TELEPHONE_NEARBY_TRUE(29), + TELEPHONE_NEARBY_FALSE(-29), + + // id:30 text:Picnic tables nearby + PICNIC_TABLES_NEARBY_TRUE(30), + PICNIC_TABLES_NEARBY_FALSE(-30), + + // id:31 text:Camping available + CAMPING_AVAILABLE_TRUE(31), + CAMPING_AVAILABLE_FALSE(-31), + + // id:32 text:Bicycles + BICYCLES_TRUE(32), + BICYCLES_FALSE(-32), + + // id:33 text:Motorcycles + MOTORCYCLES_TRUE(33), + MOTORCYCLES_FALSE(-33), + + // id:34 text:Quads + QUADS_TRUE(34), + QUADS_FALSE(-34), + + // id:35 text:Off-road vehicles + OFF_ROAD_VEHICLES_TRUE(35), + OFF_ROAD_VEHICLES_FALSE(-35), + + // id:36 text:Snowmobiles + SNOWMOBILES_TRUE(36), + SNOWMOBILES_FALSE(-36), + + // id:37 text:Horses + HORSES_TRUE(37), + HORSES_FALSE(-37), + + // id:38 text:Campfires + CAMPFIRES_TRUE(38), + CAMPFIRES_FALSE(-38), + + // id:39 text:Thorns + THORNS_TRUE(39), + THORNS_FALSE(-39), + + // id:40 text:Stealth required + STEALTH_REQUIRED_TRUE(40), + STEALTH_REQUIRED_FALSE(-40), + + // id:41 text:Stroller accessible + STROLLER_ACCESSIBLE_TRUE(41), + STROLLER_ACCESSIBLE_FALSE(-41), + + // id:43 text:Watch for livestock + WATCH_FOR_LIVESTOCK_TRUE(43), + WATCH_FOR_LIVESTOCK_FALSE(-43), + + // id:42 text:Needs maintenance + NEEDS_MAINTENANCE_TRUE(42), + NEEDS_MAINTENANCE_FALSE(-42), + + // id:44 text:Flashlight required + FLASHLIGHT_REQUIRED_TRUE(44), + FLASHLIGHT_REQUIRED_FALSE(-44), + + // id:45 text:Lost and Found Tour + LOST_AND_FOUND_TOUR_TRUE(45), + LOST_AND_FOUND_TOUR_FALSE(-45), + + // id:46 text:Truck Driver/RV + TRUCK_DRIVER_RV_TRUE(46), + TRUCK_DRIVER_RV_FALSE(-46), + + // id:47 text:Field Puzzle + FIELD_PUZZLE_TRUE(47), + FIELD_PUZZLE_FALSE(-47), + + // id:48 text:UV Light Required + UV_LIGHT_REQUIRED_TRUE(48), + UV_LIGHT_REQUIRED_FALSE(-48), + + // id:49 text:Snowshoes + SNOWSHOES_TRUE(49), + SNOWSHOES_FALSE(-49), + + // id:50 text:Cross Country Skis + CROSS_COUNTRY_SKIS_TRUE(50), + CROSS_COUNTRY_SKIS_FALSE(-50), + + // id:51 text:Special Tool Required + SPECIAL_TOOL_REQUIRED_TRUE(51), + SPECIAL_TOOL_REQUIRED_FALSE(-51), + + // id:52 text:Night Cache + NIGHT_CACHE_TRUE(52), + NIGHT_CACHE_FALSE(-52), + + // id:53 text:Park and Grab + PARK_AND_GRAB_TRUE(53), + PARK_AND_GRAB_FALSE(-53), + + // id:54 text:Abandoned Structure + ABANDONED_STRUCTURE_TRUE(54), + ABANDONED_STRUCTURE_FALSE(-54), + + // id:55 text:Short hike (less than 1km) + SHORT_HIKE_LESS_THAN_1KM_TRUE(55), + SHORT_HIKE_LESS_THAN_1KM_FALSE(-55), + + // id:56 text:Medium hike (1km-10km) + MEDIUM_HIKE_1KM_10KM_TRUE(56), + MEDIUM_HIKE_1KM_10KM_FALSE(-56), + + // id:57 text:Long Hike (+10km) + LONG_HIKE_10KM_TRUE(57), + LONG_HIKE_10KM_FALSE(-57), + + // id:58 text:Fuel Nearby + FUEL_NEARBY_TRUE(58), + FUEL_NEARBY_FALSE(-58), + + // id:59 text:Food Nearby + FOOD_NEARBY_TRUE(59), + FOOD_NEARBY_FALSE(-59), + + // id:60 text:Wireless Beacon + WIRELESS_BEACON_TRUE(60), + WIRELESS_BEACON_FALSE(-60), + + // id:61 text:Partnership cache + PARTNERSHIP_CACHE_TRUE(61), + PARTNERSHIP_CACHE_FALSE(-61), + + // id:62 text:Seasonal Access + SEASONAL_ACCESS_TRUE(62), + SEASONAL_ACCESS_FALSE(-62), + + // id:63 text:Tourist Friendly + TOURIST_FRIENDLY_TRUE(63), + TOURIST_FRIENDLY_FALSE(-63), + + // id:64 text:Tree Climbing + TREE_CLIMBING_TRUE(64), + TREE_CLIMBING_FALSE(-64), + + // id:65 text:Front Yard (Private Residence) + FRONT_YARD_PRIVATE_RESIDENCE_TRUE(65), + FRONT_YARD_PRIVATE_RESIDENCE_FALSE(-65), + + // id:66 text:Teamwork Required + TEAMWORK_REQUIRED_TRUE(66), + TEAMWORK_REQUIRED_FALSE(-66), + + // id:67 text:geotour + GEOTOUR_TRUE(67), + GEOTOUR_FALSE(-67); + + + private final static List attributes = new ArrayList<>(); + + private int id; + + private Attribute(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public static Attribute groundspeakAttributeToAttribute(Long id, boolean inc) { + + int idToCompare = id.intValue(); + + if (!inc) { + idToCompare = -idToCompare; + } + + for (Attribute t : Attribute.values()) { + if (t.getId() == idToCompare) { + return t; + } + } + + if (!attributes.contains(id)) { + attributes.add(id); + } + + throw new IllegalArgumentException("unknown attribute id:" + id + " inc:" + inc); + } +} diff --git a/src/main/java/de/geofroggerfx/model/Cache.java b/src/main/java/de/geofroggerfx/model/Cache.java new file mode 100644 index 0000000..a80fcfc --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/Cache.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +public class Cache { + private LongProperty id = new SimpleLongProperty(); + private BooleanProperty available = new SimpleBooleanProperty(); + private BooleanProperty archived = new SimpleBooleanProperty(); + private BooleanProperty found = new SimpleBooleanProperty(); + private StringProperty name = new SimpleStringProperty(); + private StringProperty placedBy = new SimpleStringProperty(); + private ObjectProperty owner = new SimpleObjectProperty<>(); + private ObjectProperty type = new SimpleObjectProperty<>(); + private ObjectProperty container = new SimpleObjectProperty<>(); + private ObjectProperty> attributes = new SimpleObjectProperty<>(FXCollections.observableArrayList()); + private StringProperty difficulty = new SimpleStringProperty(); + private StringProperty terrain = new SimpleStringProperty(); + private StringProperty country = new SimpleStringProperty(); + private StringProperty state = new SimpleStringProperty(); + private StringProperty shortDescription = new SimpleStringProperty(); + private BooleanProperty shortDescriptionHtml = new SimpleBooleanProperty(); + private StringProperty longDescription = new SimpleStringProperty(); + private BooleanProperty longDescriptionHtml = new SimpleBooleanProperty(); + private StringProperty encodedHints = new SimpleStringProperty(); + private ObjectProperty mainWayPoint = new SimpleObjectProperty<>(); + + public long getId() { + return id.get(); + } + + public LongProperty idProperty() { + return id; + } + + public void setId(long id) { + this.id.set(id); + } + + public boolean getAvailable() { + return available.get(); + } + + public BooleanProperty availableProperty() { + return available; + } + + public void setAvailable(boolean available) { + this.available.set(available); + } + + public boolean getArchived() { + return archived.get(); + } + + public BooleanProperty archivedProperty() { + return archived; + } + + public void setArchived(boolean archived) { + this.archived.set(archived); + } + + public boolean getFound() { + return found.get(); + } + + public BooleanProperty foundProperty() { + return found; + } + + public void setFound(boolean found) { + this.found.set(found); + } + + public String getName() { + return name.get(); + } + + public StringProperty nameProperty() { + return name; + } + + public void setName(String name) { + this.name.set(name); + } + + public String getPlacedBy() { + return placedBy.get(); + } + + public StringProperty placedByProperty() { + return placedBy; + } + + public void setPlacedBy(String placedBy) { + this.placedBy.set(placedBy); + } + + public User getOwner() { + return owner.get(); + } + + public ObjectProperty ownerProperty() { + return owner; + } + + public void setOwner(User owner) { + this.owner.set(owner); + } + + public Type getType() { + return type.get(); + } + + public ObjectProperty typeProperty() { + return type; + } + + public void setType(Type type) { + this.type.set(type); + } + + public Container getContainer() { + return container.get(); + } + + public ObjectProperty containerProperty() { + return container; + } + + public void setContainer(Container container) { + this.container.set(container); + } + + public ObservableList getAttributes() { + return attributes.get(); + } + + public ObjectProperty> attributesProperty() { + return attributes; + } + + public void setAttributes(ObservableList attributes) { + this.attributes.set(attributes); + } + + public String getDifficulty() { + return difficulty.get(); + } + + public StringProperty difficultyProperty() { + return difficulty; + } + + public void setDifficulty(String difficulty) { + this.difficulty.set(difficulty); + } + + public String getTerrain() { + return terrain.get(); + } + + public StringProperty terrainProperty() { + return terrain; + } + + public void setTerrain(String terrain) { + this.terrain.set(terrain); + } + + public String getCountry() { + return country.get(); + } + + public StringProperty countryProperty() { + return country; + } + + public void setCountry(String country) { + this.country.set(country); + } + + public String getState() { + return state.get(); + } + + public StringProperty stateProperty() { + return state; + } + + public void setState(String state) { + this.state.set(state); + } + + public String getShortDescription() { + return shortDescription.get(); + } + + public StringProperty shortDescriptionProperty() { + return shortDescription; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription.set(shortDescription); + } + + public boolean getShortDescriptionHtml() { + return shortDescriptionHtml.get(); + } + + public BooleanProperty shortDescriptionHtmlProperty() { + return shortDescriptionHtml; + } + + public void setShortDescriptionHtml(boolean shortDescriptionHtml) { + this.shortDescriptionHtml.set(shortDescriptionHtml); + } + + public String getLongDescription() { + return longDescription.get(); + } + + public StringProperty longDescriptionProperty() { + return longDescription; + } + + public void setLongDescription(String longDescription) { + this.longDescription.set(longDescription); + } + + public boolean getLongDescriptionHtml() { + return longDescriptionHtml.get(); + } + + public BooleanProperty longDescriptionHtmlProperty() { + return longDescriptionHtml; + } + + public void setLongDescriptionHtml(boolean longDescriptionHtml) { + this.longDescriptionHtml.set(longDescriptionHtml); + } + + public String getEncodedHints() { + return encodedHints.get(); + } + + public StringProperty encodedHintsProperty() { + return encodedHints; + } + + public void setEncodedHints(String encodedHints) { + this.encodedHints.set(encodedHints); + } + + public Waypoint getMainWayPoint() { + return mainWayPoint.get(); + } + + public ObjectProperty mainWayPointProperty() { + return mainWayPoint; + } + + public void setMainWayPoint(Waypoint mainWayPoint) { + this.mainWayPoint.set(mainWayPoint); + } + + @Override + public String toString() { + return "Cache{" + + "name=" + name + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Cache cache = (Cache) o; + + if (id != null ? !id.equals(cache.id) : cache.id != null) return false; + + return true; + } + + @Override + public int hashCode() { + return id != null ? id.hashCode() : 0; + } +} diff --git a/src/main/java/de/geofroggerfx/model/CacheListEntry.java b/src/main/java/de/geofroggerfx/model/CacheListEntry.java new file mode 100644 index 0000000..cd17648 --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/CacheListEntry.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import javafx.beans.property.*; + +/** + * Created by Andreas on 10.03.2015. + */ +public class CacheListEntry { + + private LongProperty id = new SimpleLongProperty(); + private StringProperty name = new SimpleStringProperty(); + private StringProperty difficulty = new SimpleStringProperty(); + private StringProperty terrain = new SimpleStringProperty(); + private StringProperty code = new SimpleStringProperty(); + private ObjectProperty type = new SimpleObjectProperty<>(); + + public CacheListEntry(long id, + String name, + String code, + String difficulty, + String terrain, + Type type) { + this.id.setValue(id); + this.name.setValue(name); + this.code.setValue(code); + this.difficulty.setValue(difficulty); + this.terrain.setValue(terrain); + this.type.setValue(type); + } + + public String getName() { + return name.get(); + } + + public ReadOnlyStringProperty nameProperty() { + return name; + } + + public String getCode() { + return code.get(); + } + + public ReadOnlyStringProperty codeProperty() { + return code; + } + + public long getId() { + return id.get(); + } + + public ReadOnlyLongProperty idProperty() { + return id; + } + + public String getDifficulty() { + return difficulty.get(); + } + + public ReadOnlyStringProperty difficultyProperty() { + return difficulty; + } + + public String getTerrain() { + return terrain.get(); + } + + public ReadOnlyStringProperty terrainProperty() { + return terrain; + } + + public Type getType() { + return type.get(); + } + + public ReadOnlyObjectProperty typeProperty() { + return type; + } + + @Override + public String toString() { + return "CacheListEntry{" + + "name=" + name + + '}'; + } +} diff --git a/src/main/java/de/geofroggerfx/model/Container.java b/src/main/java/de/geofroggerfx/model/Container.java new file mode 100644 index 0000000..b797213 --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/Container.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import groovy.lang.Closure; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +/** + * Created by Andreas Billmann on 22.02.2015. + */ +public enum Container { + SMALL("Small"), + MICRO("Micro"), + REGULAR("Regular"), + OTHER("Other"), + NOT_CHOSEN("Not chosen"), + LARGE("Large"), + VIRTUAL("Virtual"); + + + private int id; + private String groundspeakString; + + private Container(String groundspeakString) { + this.groundspeakString = groundspeakString; + } + + public String toGroundspeakString() { + return groundspeakString; + } + + public static Container groundspeakStringToContainer(String groundspeakString) { + for (Container t : Container.values()) { + if (t.toGroundspeakString().equals(groundspeakString)) { + return t; + } + } + throw new IllegalArgumentException("unknown container:" + groundspeakString); + } + +} diff --git a/src/main/java/de/geofroggerfx/model/Type.java b/src/main/java/de/geofroggerfx/model/Type.java new file mode 100644 index 0000000..13329bd --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/Type.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import groovy.lang.Closure; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; + +/** + * Created by Andreas Billmann on 22.02.2015. + */ +public enum Type { + TRADITIONAL_CACHE("Traditional Cache"), + MULTI_CACHE("Multi-cache"), + UNKNOWN_CACHE("Unknown Cache"), + EARTH_CACHE("Earthcache"), + LETTERBOX("Letterbox Hybrid"), + EVENT("Event Cache"), + WHERIGO("Wherigo Cache"), + WEBCAM_CACHE("Webcam Cache"), + VIRTUAL_CACHE("Virtual Cache"), + CITO_EVENT("Cache In Trash Out Event"), + MEGA_EVENT("Mega-Event Cache"), + MYSTERY_CACHE("Mystery Cache"); + + private int id; + private String groundspeakString; + + private Type(String groundspeakString) { + this.groundspeakString = groundspeakString; + } + + public String toGroundspeakString() { + return groundspeakString; + } + + public static Type groundspeakStringToType(String groundspeakString) { + for (Type t : Type.values()) { + if (t.toGroundspeakString().equals(groundspeakString)) { + return t; + } + } + throw new IllegalArgumentException("unknown type:" + groundspeakString); + } + +} diff --git a/src/main/java/de/geofroggerfx/model/User.java b/src/main/java/de/geofroggerfx/model/User.java new file mode 100644 index 0000000..47ed431 --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/User.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import javafx.beans.property.LongProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/** + * Created by Andreas Billmann on 22.02.2015. + */ +public class User { + private LongProperty id = new SimpleLongProperty(); + private StringProperty name = new SimpleStringProperty(); + + public long getId() { + return id.get(); + } + + public LongProperty idProperty() { + return id; + } + + public void setId(long id) { + this.id.set(id); + } + + public String getName() { + return name.get(); + } + + public StringProperty nameProperty() { + return name; + } + + public void setName(String name) { + this.name.set(name); + } +} diff --git a/src/main/java/de/geofroggerfx/model/Waypoint.java b/src/main/java/de/geofroggerfx/model/Waypoint.java new file mode 100644 index 0000000..1c628ac --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/Waypoint.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +import javafx.beans.property.*; + +import java.util.Date; + +/** + * Created by Andreas Billmann on 22.02.2015. + */ +public class Waypoint { + private ObjectProperty cache = new SimpleObjectProperty<>(); + private DoubleProperty latitude = new SimpleDoubleProperty(); + private DoubleProperty longitude = new SimpleDoubleProperty(); + private StringProperty name = new SimpleStringProperty(); + private ObjectProperty time = new SimpleObjectProperty<>(); + private StringProperty description = new SimpleStringProperty(); + private StringProperty url = new SimpleStringProperty(); + private StringProperty urlName = new SimpleStringProperty(); + private StringProperty symbol = new SimpleStringProperty(); + private ObjectProperty type = new SimpleObjectProperty<>(); + + public Cache getCache() { + return cache.get(); + } + + public ObjectProperty cacheProperty() { + return cache; + } + + public void setCache(Cache cache) { + this.cache.set(cache); + } + + public double getLatitude() { + return latitude.get(); + } + + public DoubleProperty latitudeProperty() { + return latitude; + } + + public void setLatitude(double latitude) { + this.latitude.set(latitude); + } + + public double getLongitude() { + return longitude.get(); + } + + public DoubleProperty longitudeProperty() { + return longitude; + } + + public void setLongitude(double longitude) { + this.longitude.set(longitude); + } + + public String getName() { + return name.get(); + } + + public StringProperty nameProperty() { + return name; + } + + public void setName(String name) { + this.name.set(name); + } + + public Date getTime() { + return time.get(); + } + + public ObjectProperty timeProperty() { + return time; + } + + public void setTime(Date time) { + this.time.set(time); + } + + public String getDescription() { + return description.get(); + } + + public StringProperty descriptionProperty() { + return description; + } + + public void setDescription(String description) { + this.description.set(description); + } + + public String getUrl() { + return url.get(); + } + + public StringProperty urlProperty() { + return url; + } + + public void setUrl(String url) { + this.url.set(url); + } + + public String getUrlName() { + return urlName.get(); + } + + public StringProperty urlNameProperty() { + return urlName; + } + + public void setUrlName(String urlName) { + this.urlName.set(urlName); + } + + public String getSymbol() { + return symbol.get(); + } + + public StringProperty symbolProperty() { + return symbol; + } + + public void setSymbol(String symbol) { + this.symbol.set(symbol); + } + + public WaypointType getType() { + return type.get(); + } + + public ObjectProperty typeProperty() { + return type; + } + + public void setType(WaypointType type) { + this.type.set(type); + } +} diff --git a/src/main/java/de/geofroggerfx/model/WaypointType.java b/src/main/java/de/geofroggerfx/model/WaypointType.java new file mode 100644 index 0000000..c304536 --- /dev/null +++ b/src/main/java/de/geofroggerfx/model/WaypointType.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.model; + +/** + * Created by Andreas Billmann on 22.02.2015. + */ +public enum WaypointType { + +} diff --git a/src/main/java/de/geofroggerfx/plugins/Plugin.java b/src/main/java/de/geofroggerfx/plugins/Plugin.java new file mode 100644 index 0000000..93751ef --- /dev/null +++ b/src/main/java/de/geofroggerfx/plugins/Plugin.java @@ -0,0 +1,31 @@ +package de.geofroggerfx.plugins; + +import java.util.Map; + +/** + * This interface defines the method a plugin has to provide to be called correctly + * + * @author abi + */ +public interface Plugin { + + /** + * @return name + */ + String getName(); + + /** + * @return version + */ + String getVersion(); + + /** + * Run the main method of the plugin. All the logic is done in this method. + * Every run method will get a context map, with all the services inside, + * to use them. + * + * @param context services and data + */ + void run(Map context); + +} diff --git a/src/main/java/de/geofroggerfx/service/CacheService.java b/src/main/java/de/geofroggerfx/service/CacheService.java new file mode 100644 index 0000000..d6b8a80 --- /dev/null +++ b/src/main/java/de/geofroggerfx/service/CacheService.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.service; + +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.model.CacheListEntry; + +import java.util.List; + +/** + * Created by Andreas on 10.03.2015. + */ + +public interface CacheService { + + + void storeCaches(List cacheList); + + List getAllCacheEntriesSortBy(String name, String asc); + + Cache getCacheForId(long id); + + List getAllCaches(); +} diff --git a/src/main/java/de/geofroggerfx/service/CacheServiceImpl.java b/src/main/java/de/geofroggerfx/service/CacheServiceImpl.java new file mode 100644 index 0000000..a675b72 --- /dev/null +++ b/src/main/java/de/geofroggerfx/service/CacheServiceImpl.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.service; + +import de.geofroggerfx.dao.CacheDAO; +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.model.CacheListEntry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Created by Andreas on 10.03.2015. + */ +@Service +public class CacheServiceImpl implements CacheService { + + @Autowired + private CacheDAO cacheDAO; + + @Override + public void storeCaches(List cacheList) { + cacheDAO.save(cacheList); + } + + @Override + public List getAllCacheEntriesSortBy(String name, String asc) { + return cacheDAO.getAllCachEntriesSortBy(name, asc); + } + + @Override + public Cache getCacheForId(long id) { + return cacheDAO.getCacheForId(id); + } + + @Override + public List getAllCaches() { + return cacheDAO.getAllCaches(); + } + + +} diff --git a/src/main/java/de/geofroggerfx/service/PluginService.java b/src/main/java/de/geofroggerfx/service/PluginService.java new file mode 100644 index 0000000..6931bc6 --- /dev/null +++ b/src/main/java/de/geofroggerfx/service/PluginService.java @@ -0,0 +1,19 @@ +package de.geofroggerfx.service; + +import de.geofroggerfx.plugins.Plugin; + +import java.util.List; + +/** + * TODO: class description + * + * @author abi + */ +public interface PluginService { + + List getAllPlugins(); + + void executePlugin(Plugin plugin); + + +} diff --git a/src/main/java/de/geofroggerfx/service/PluginServiceImpl.java b/src/main/java/de/geofroggerfx/service/PluginServiceImpl.java new file mode 100644 index 0000000..4b966b4 --- /dev/null +++ b/src/main/java/de/geofroggerfx/service/PluginServiceImpl.java @@ -0,0 +1,79 @@ +package de.geofroggerfx.service; + +import de.geofroggerfx.application.SessionContext; +import de.geofroggerfx.dao.jdbc.DataConfig; +import de.geofroggerfx.plugins.Plugin; +import groovy.lang.GroovyClassLoader; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This service find, load and executes plugins based on the plugin interface. + * + * @author abi + */ +@Service +public class PluginServiceImpl implements PluginService { + + private final GroovyClassLoader gcl = new GroovyClassLoader(); + + @Autowired + private CacheService cacheService; + + @Autowired + private SessionContext sessionContext; + + private JdbcTemplate jdbcTemplate; + + @Autowired + public void setDataConfig(DataConfig dataConfig) { + this.jdbcTemplate = new JdbcTemplate(dataConfig.dataSource()); + } + + + @Override + public List getAllPlugins() { + + List plugins = new ArrayList<>(); + + try { + File file = new File("./plugins"); + if (!file.exists()) { + throw new IllegalArgumentException("plugins folder does not exist"); + } + + File[] pluginFiles = file.listFiles((dir, name) -> name.endsWith("Plugin.groovy")); + for (File pluginFile : pluginFiles) { + Class clazz = gcl.parseClass(pluginFile); + for (Class interf : clazz.getInterfaces()) { + if (interf.equals(Plugin.class)) { + plugins.add((Plugin) clazz.newInstance()); + break; + } + } + } + + } catch (IOException | InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + + return plugins; + } + + @Override + public void executePlugin(final Plugin plugin) { + Map context = new HashMap<>(); + context.put("sessionContext", sessionContext); + context.put("cacheService", cacheService); + context.put("jdbcTemplate", jdbcTemplate); + plugin.run(context); + } +} diff --git a/src/main/java/de/geofroggerfx/ui/FXMLController.java b/src/main/java/de/geofroggerfx/ui/FXMLController.java new file mode 100644 index 0000000..337a9d1 --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/FXMLController.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui; + +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ResourceBundle; + +public abstract class FXMLController implements InitializingBean, Initializable { + + protected Node view; + protected String fxmlFilePath; + protected String resourcePath; + + public abstract void setFxmlFilePath(String filePath); + + @Value("${resource.main}") + public void setResourceBundle(String resourcePath) { + this.resourcePath = resourcePath; + } + + @Override + public void afterPropertiesSet() throws Exception { + loadFXML(); + } + + protected final void loadFXML() throws IOException { + try (InputStream fxmlStream = getClass().getResourceAsStream(fxmlFilePath)) { + FXMLLoader loader = new FXMLLoader(); + loader.setResources(ResourceBundle.getBundle(this.resourcePath)); + loader.setController(this); + this.view = (loader.load(fxmlStream)); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public Node getView() { + return view; + } +} \ No newline at end of file diff --git a/src/main/java/de/geofroggerfx/ui/GeoFroggerFX.java b/src/main/java/de/geofroggerfx/ui/GeoFroggerFX.java new file mode 100644 index 0000000..64f53f7 --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/GeoFroggerFX.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui; + +import javafx.application.Application; +import javafx.event.EventHandler; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.text.Font; +import javafx.stage.Stage; +//import org.scenicview.ScenicView; +import org.springframework.context.annotation.*; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; + +@Configuration +@ComponentScan("de.geofroggerfx") +@PropertySource(value = "classpath:/de/geofroggerfx/ui/application.properties") +public class GeoFroggerFX extends Application { + + private AnnotationConfigApplicationContext appContext; + + @Override + public void start(Stage primaryStage) throws Exception { + + loadCustomFonts(); + + appContext = new AnnotationConfigApplicationContext(GeoFroggerFX.class); + String name = appContext.getEnvironment().getProperty("application.name"); + String version = appContext.getEnvironment().getProperty("application.version"); + GeoFroggerFXController geoFroggerFXController = appContext.getBean(GeoFroggerFXController.class); + + Scene scene = new Scene((Parent) geoFroggerFXController.getView()); + + scene.setOnKeyPressed(new EventHandler() { + + @Override + public void handle(KeyEvent event) { + if (isScenicViewShortcutPressed(event)) { +// ScenicView.show(scene); + } + } + }); + primaryStage.setScene(scene); + primaryStage.setTitle(String.format("%s %s", name, version)); + primaryStage.show(); + } + + @Bean + public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Override + public void stop() throws Exception { + if (appContext != null) { + appContext.close(); + } + + super.stop(); + } + + public static void main(String[] args) { + launch(args); + } + + private static boolean isScenicViewShortcutPressed(final KeyEvent keyEvent) { + return keyEvent.isAltDown() && keyEvent.isControlDown() && keyEvent.getCode().equals(KeyCode.V); + } + + private void loadCustomFonts() { + Font.loadFont(GeoFroggerFX.class.getResource("/fonts/sofia/Sofia-Regular.otf").toExternalForm(), 24); + } +} diff --git a/src/main/java/de/geofroggerfx/ui/GeoFroggerFXController.java b/src/main/java/de/geofroggerfx/ui/GeoFroggerFXController.java new file mode 100644 index 0000000..9c024c7 --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/GeoFroggerFXController.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui; + +import de.geofroggerfx.application.ProgressEvent; +import de.geofroggerfx.application.SessionContext; +import de.geofroggerfx.gpx.GPXReader; +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.model.CacheListEntry; +import de.geofroggerfx.plugins.Plugin; +import de.geofroggerfx.service.CacheService; +import de.geofroggerfx.service.PluginService; +import de.geofroggerfx.ui.details.DetailsController; +import de.geofroggerfx.ui.list.ListController; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.concurrent.Service; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.stage.FileChooser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static de.geofroggerfx.application.SessionContext.*; + +/** + * Created by Andreas on 09.03.2015. + */ +@Component +public class GeoFroggerFXController extends FXMLController { + + private static final Logger LOGGER = Logger.getLogger(GeoFroggerFXController.class.getName()); + + @Autowired + private CacheService cacheService; + + @Autowired + private SessionContext sessionContext; + + @Autowired + private ListController listController; + + @Autowired + private DetailsController detailsController; + + @Autowired + private PluginService pluginService; + + @Autowired + private GPXReader reader; + + @FXML + private HBox cacheListContent; + + @FXML + private AnchorPane cacheDetailsContent; + + @FXML + private Label leftStatus; + + @FXML + private ProgressBar progress; + + @FXML + private Menu pluginsMenu; + + private ResourceBundle resourceBundle; + + private final LoadCachesFromFileService loadService = new LoadCachesFromFileService(); + private final LoadCacheListsFromDatabaseService loadListsFromDBService = new LoadCacheListsFromDatabaseService(); + + @Value("${fxml.geofrogger.view}") + @Override + public void setFxmlFilePath(String filePath) { + this.fxmlFilePath = filePath; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + this.resourceBundle = resources; + + LOGGER.info("load all sub controller"); + cacheListContent.getChildren().add(listController.getView()); + cacheDetailsContent.getChildren().add(detailsController.getView()); + + LOGGER.info("add listeners"); + reader.addListener((ProgressEvent event) -> updateStatus(event.getMessage(), event.getProgress())); + + loadListsFromDBService.start(); + + List plugins = pluginService.getAllPlugins(); + for (Plugin plugin: plugins) { + MenuItem menuItem = new MenuItem(plugin.getName()+" ("+plugin.getVersion()+")"); + menuItem.setOnAction(actionEvent -> pluginService.executePlugin(plugin)); + pluginsMenu.getItems().add(menuItem); + } + + } + + + @FXML + public void importGpx(ActionEvent actionEvent) { + final FileChooser fileChooser = new FileChooser(); + + //Set extension filter + final FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("GPX files (*.gpx)", "*.gpx"); + fileChooser.getExtensionFilters().add(extFilter); + + //Show open file dialog + final File file = fileChooser.showOpenDialog(null); + loadService.setFile(file); + loadService.restart(); + } + + @FXML + public void showAboutDialog(ActionEvent actionEvent) { +// Dialog dialog = new Dialog(null, resourceBundle.getString("dialog.title.about")); +// dialog.setMasthead(MASTHEAD_TEXT); +// dialog.setContent(ABOUT_TEXT); +// dialog.setExpandableContent(new TextArea(LICENSE)); +// dialog.show(); + } + + @FXML + public void showSettings(ActionEvent actionEvent) { +// mainPane.setCenter(); + +// FXMLLoader.load() + } + + @FXML + public void newList(ActionEvent actionEvent) { +// final Optional listNameOption = Dialogs. +// create(). +// title(resourceBundle.getString("dialog.title.new_list")). +// message(resourceBundle.getString("dialog.label.listname")). +// showTextInput(); +// if (hasValue(listNameOption)) { +// final String listName = listNameOption.get().trim(); +// if (cacheService.doesCacheListNameExist(listName)) { +// Dialogs. +// create(). +// message(resourceBundle.getString("dialog.msg.list.does.exist")). +// showError(); +// } else { +// CacheList list = new CacheList(); +// list.setName(listName); +// cacheService.storeCacheList(list); +// setCacheListInContext(); +// } +// } + } + + @FXML + public void deleteList(ActionEvent actionEvent) { +// final Optional listOption = Dialogs. +// create(). +// title("dialog.title.delete_list"). +// message("dialog.label.listname"). +// showChoices(cacheService.getAllCacheLists()); +// +// if (listOption.isPresent()) { +// cacheService.deleteCacheList(listOption.get()); +// setCacheListInContext(); +// } + } + + @FXML + public void exit(ActionEvent actionEvent) { + Platform.exit(); + } + + private void updateStatus(String text, double progressValue) { + Platform.runLater(() -> { + leftStatus.setText(text); + progress.setProgress(progressValue); + }); + } + + + private class LoadCachesFromFileService extends Service { + + private final ObjectProperty file = new SimpleObjectProperty(); + + public final void setFile(File value) { + file.set(value); + } + + public final File getFile() { + return file.get(); + } + + public final ObjectProperty fileProperty() { + return file; + } + + @Override + protected Task createTask() { + return new Task() { + @Override + protected Void call() throws Exception { + try { + LOGGER.info("load from file "+file.get().getAbsolutePath()); + final List cacheList = reader.load(file.get()); + LOGGER.info(cacheList.size()+" caches loaded"); + if (!cacheList.isEmpty()) { + updateStatus(resourceBundle.getString("status.store.all.caches"), ProgressIndicator.INDETERMINATE_PROGRESS); + cacheService.storeCaches(cacheList); + updateStatus(resourceBundle.getString("status.all.caches.stored"), 0); + LOGGER.info("caches stored in database"); + + loadAllCacheEntries(); + } + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, ex.getMessage() ,ex); + } + return null; + } + }; + } + + } + + private class LoadCacheListsFromDatabaseService extends Service { + + @Override + protected Task createTask() { + return new Task() { + @Override + protected Void call() throws Exception { + loadAllCacheEntries(); + return null; + } + }; + } + + } + + private void loadAllCacheEntries() { + updateStatus(resourceBundle.getString("status.load.caches.from.db"), ProgressIndicator.INDETERMINATE_PROGRESS); + sessionContext.setData(CACHE_ENTRY_LIST, cacheService.getAllCacheEntriesSortBy("name", "asc")); + updateStatus(resourceBundle.getString("status.all.caches.loaded"), 0); + } +} diff --git a/src/main/java/de/geofroggerfx/ui/GeocachingIcons.java b/src/main/java/de/geofroggerfx/ui/GeocachingIcons.java new file mode 100644 index 0000000..4c604b6 --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/GeocachingIcons.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui; + +import de.geofroggerfx.model.Attribute; +import de.geofroggerfx.model.Type; +import de.jensd.fx.glyphs.GlyphIcon; +import de.jensd.fx.glyphs.GlyphIcons; +import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcons; +import javafx.scene.text.Text; + +import java.util.HashMap; +import java.util.Map; + +import static de.geofroggerfx.model.Attribute.*; +import static de.geofroggerfx.model.Type.*; +import static de.geofroggerfx.ui.glyphs.GeofroggerGlyphsDude.createIcon; +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcons.*; +import static de.jensd.fx.glyphs.weathericons.WeatherIcons.*; + +/** + * @author Andreas + */ +public class GeocachingIcons { + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // cache attribute + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private static final Map attributeMap; + + static { + attributeMap = new HashMap<>(); + attributeMap.put(DOGS_TRUE, PAW); + attributeMap.put(DOGS_FALSE, PAW); + // attributeMap.put(ACCESS_OR_PARKING_FEE_TRUE,); + // attributeMap.put(ACCESS_OR_PARKING_FEE_FALSE, ); + // attributeMap.put(CLIMBING_GEAR_TRUE,); + // attributeMap.put(CLIMBING_GEAR_FALSE,); + attributeMap.put(BOAT_TRUE, SHIP); + attributeMap.put(BOAT_FALSE, SHIP); + // attributeMap.put(SCUBA_GEAR_TRUE(5); + // attributeMap.put(SCUBA_GEAR_FALSE(-5); + attributeMap.put(RECOMMENDED_FOR_KIDS_TRUE, CHILD); + attributeMap.put(RECOMMENDED_FOR_KIDS_FALSE, CHILD); + attributeMap.put(TAKES_LESS_THAN_AN_HOUR_TRUE, TIME_1); + attributeMap.put(TAKES_LESS_THAN_AN_HOUR_FALSE, TIME_1); + attributeMap.put(SCENIC_VIEW_TRUE, EYE); + attributeMap.put(SCENIC_VIEW_FALSE, EYE); + // attributeMap.put(SIGNIFICANT_HIKE_TRUE(9); + // attributeMap.put(SIGNIFICANT_HIKE_FALSE(-9); + // attributeMap.put(DIFFICULT_CLIMBING_TRUE(10); + // attributeMap.put(DIFFICULT_CLIMBING_FALSE(-10); + // attributeMap.put(MAY_REQUIRE_WADING_TRUE(11); + // attributeMap.put(MAY_REQUIRE_WADING_FALSE(-11); + // attributeMap.put(MAY_REQUIRE_SWIMMING_TRUE(12); + // attributeMap.put(MAY_REQUIRE_SWIMMING_FALSE(-12); + attributeMap.put(AVAILABLE_AT_ALL_TIMES_TRUE, TIME_12); + attributeMap.put(AVAILABLE_AT_ALL_TIMES_FALSE, TIME_12); + attributeMap.put(RECOMMENDED_AT_NIGHT_TRUE, MOON_ALT); + attributeMap.put(RECOMMENDED_AT_NIGHT_FALSE, MOON_ALT); + attributeMap.put(AVAILABLE_DURING_WINTER_TRUE, SNOW); + attributeMap.put(AVAILABLE_DURING_WINTER_FALSE, SNOW); + // attributeMap.put(POISON_PLANTS_TRUE(17); + // attributeMap.put(POISON_PLANTS_FALSE(-17); + // attributeMap.put(DANGEROUS_ANIMALS_TRUE(18); + // attributeMap.put(DANGEROUS_ANIMALS_FALSE(-18); + // attributeMap.put(TICKS_TRUE(19); + // attributeMap.put(TICKS_FALSE(-19); + // attributeMap.put(ABANDONED_MINES_TRUE(20); + // attributeMap.put(ABANDONED_MINES_FALSE(-20); + attributeMap.put(CLIFF_FALLING_ROCKS_TRUE, METEOR); + attributeMap.put(CLIFF_FALLING_ROCKS_FALSE, METEOR); + // attributeMap.put(HUNTING_TRUE(22); + // attributeMap.put(HUNTING_FALSE(-22); + // attributeMap.put(DANGEROUS_AREA_TRUE(23); + // attributeMap.put(DANGEROUS_AREA_FALSE(-23); + attributeMap.put(WHEELCHAIR_ACCESSIBLE_TRUE, WHEELCHAIR); + attributeMap.put(WHEELCHAIR_ACCESSIBLE_FALSE, WHEELCHAIR); + // attributeMap.put(PARKING_AVAILABLE_TRUE(25); + // attributeMap.put(PARKING_AVAILABLE_FALSE(-25); + attributeMap.put(PUBLIC_TRANSPORTATION_TRUE, BUS); + attributeMap.put(PUBLIC_TRANSPORTATION_FALSE, BUS); + // attributeMap.put(DRINKING_WATER_NEARBY_TRUE(27); + // attributeMap.put(DRINKING_WATER_NEARBY_FALSE(-27); + // attributeMap.put(PUBLIC_RESTROOMS_NEARBY_TRUE(28); + // attributeMap.put(PUBLIC_RESTROOMS_NEARBY_FALSE(-28); + attributeMap.put(TELEPHONE_NEARBY_TRUE, PHONE); + attributeMap.put(TELEPHONE_NEARBY_FALSE, PHONE); + // attributeMap.put(PICNIC_TABLES_NEARBY_TRUE(30); + // attributeMap.put(PICNIC_TABLES_NEARBY_FALSE(-30); + // attributeMap.put(CAMPING_AVAILABLE_TRUE(31); + // attributeMap.put(CAMPING_AVAILABLE_FALSE(-31); + attributeMap.put(BICYCLES_TRUE, BICYCLE); + attributeMap.put(BICYCLES_FALSE, BICYCLE); + attributeMap.put(MOTORCYCLES_TRUE, MOTORCYCLE); + attributeMap.put(MOTORCYCLES_FALSE, MOTORCYCLE); + // attributeMap.put(QUADS_TRUE(34); + // attributeMap.put(QUADS_FALSE(-34); + // attributeMap.put(OFF_ROAD_VEHICLES_TRUE(35); + // attributeMap.put(OFF_ROAD_VEHICLES_FALSE(-35); + // attributeMap.put(SNOWMOBILES_TRUE(36); + // attributeMap.put(SNOWMOBILES_FALSE(-36); + // attributeMap.put(HORSES_TRUE(37); + // attributeMap.put(HORSES_FALSE(-37); + // attributeMap.put(CAMPFIRES_TRUE(38); + // attributeMap.put(CAMPFIRES_FALSE(-38); + // attributeMap.put(THORNS_TRUE(39); + // attributeMap.put(THORNS_FALSE(-39); + // attributeMap.put(STEALTH_REQUIRED_TRUE(40); + // attributeMap.put(STEALTH_REQUIRED_FALSE(-40); + // attributeMap.put(STROLLER_ACCESSIBLE_TRUE(41); + // attributeMap.put(STROLLER_ACCESSIBLE_FALSE(-41); + // attributeMap.put(WATCH_FOR_LIVESTOCK_TRUE(43); + // attributeMap.put(WATCH_FOR_LIVESTOCK_FALSE(-43); + // attributeMap.put(NEEDS_MAINTENANCE_TRUE(42); + // attributeMap.put(NEEDS_MAINTENANCE_FALSE(-42); + // attributeMap.put(FLASHLIGHT_REQUIRED_TRUE(44); + // attributeMap.put(FLASHLIGHT_REQUIRED_FALSE(-44); + // attributeMap.put(LOST_AND_FOUND_TOUR_TRUE(45); + // attributeMap.put(LOST_AND_FOUND_TOUR_FALSE(-45); + // attributeMap.put(TRUCK_DRIVER_RV_TRUE(46); + // attributeMap.put(TRUCK_DRIVER_RV_FALSE(-46); + // attributeMap.put(FIELD_PUZZLE_TRUE(47); + // attributeMap.put(FIELD_PUZZLE_FALSE(-47); + // attributeMap.put(UV_LIGHT_REQUIRED_TRUE(48); + // attributeMap.put(UV_LIGHT_REQUIRED_FALSE(-48); + // attributeMap.put(SNOWSHOES_TRUE(49); + // attributeMap.put(SNOWSHOES_FALSE(-49); + // attributeMap.put(CROSS_COUNTRY_SKIS_TRUE(50); + // attributeMap.put(CROSS_COUNTRY_SKIS_FALSE(-50); + // attributeMap.put(SPECIAL_TOOL_REQUIRED_TRUE(51); + // attributeMap.put(SPECIAL_TOOL_REQUIRED_FALSE(-51); + attributeMap.put(NIGHT_CACHE_TRUE, MOON_WANING_CRESCENT_2); + attributeMap.put(NIGHT_CACHE_FALSE, MOON_WANING_CRESCENT_2); + // attributeMap.put(PARK_AND_GRAB_TRUE(53); + // attributeMap.put(PARK_AND_GRAB_FALSE(-53); + // attributeMap.put(ABANDONED_STRUCTURE_TRUE(54); + // attributeMap.put(ABANDONED_STRUCTURE_FALSE(-54); + // attributeMap.put(SHORT_HIKE_LESS_THAN_1KM_TRUE(55); + // attributeMap.put(SHORT_HIKE_LESS_THAN_1KM_FALSE(-55); + // attributeMap.put(MEDIUM_HIKE_1KM_10KM_TRUE(56); + // attributeMap.put(MEDIUM_HIKE_1KM_10KM_FALSE(-56); + // attributeMap.put(LONG_HIKE_10KM_TRUE(57); + // attributeMap.put(LONG_HIKE_10KM_FALSE(-57); + // attributeMap.put(FUEL_NEARBY_TRUE(58); + // attributeMap.put(FUEL_NEARBY_FALSE(-58); + // attributeMap.put(FOOD_NEARBY_TRUE(59); + // attributeMap.put(FOOD_NEARBY_FALSE(-59); + // attributeMap.put(WIRELESS_BEACON_TRUE(60); + // attributeMap.put(WIRELESS_BEACON_FALSE(-60); + // attributeMap.put(PARTNERSHIP_CACHE_TRUE(61); + // attributeMap.put(PARTNERSHIP_CACHE_FALSE(-61); + attributeMap.put(SEASONAL_ACCESS_TRUE, THERMOMETER); + attributeMap.put(SEASONAL_ACCESS_FALSE, THERMOMETER); + // attributeMap.put(TOURIST_FRIENDLY_TRUE(63); + // attributeMap.put(TOURIST_FRIENDLY_FALSE(-63); + // attributeMap.put(TREE_CLIMBING_TRUE(64); + // attributeMap.put(TREE_CLIMBING_FALSE(-64); + // attributeMap.put(FRONT_YARD_PRIVATE_RESIDENCE_TRUE(65); + // attributeMap.put(FRONT_YARD_PRIVATE_RESIDENCE_FALSE(-65); + // attributeMap.put(TEAMWORK_REQUIRED_TRUE(66); + // attributeMap.put(TEAMWORK_REQUIRED_FALSE(-66); + // attributeMap.put(GEOTOUR_TRUE(67); + // attributeMap.put(GEOTOUR_FALSE(-67); + } + + public static Text getIconAsText(Attribute attribute, String iconSize) { + return createIcon(getIcon(attribute), iconSize); + } + + public static Text getIconAsText(Attribute attribute) { + return getIconAsText(attribute, GlyphIcon.DEFAULT_ICON_SIZE); + } + + public static String getIconAsString(Attribute attribute) { + return getIcon(attribute).characterToString(); + } + + public static GlyphIcons getIcon(Attribute attribute) { + GlyphIcons iconName = attributeMap.get(attribute); + if (iconName == null) { + iconName = FontAwesomeIcons.BLANK; + } + return iconName; + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // cache type + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + private static final Map typeMap; + + static { + typeMap = new HashMap<>(); + typeMap.put(MULTI_CACHE, TAGS); + typeMap.put(TRADITIONAL_CACHE, TAG); + typeMap.put(UNKNOWN_CACHE, QUESTION); + typeMap.put(EARTH_CACHE, GLOBE); + typeMap.put(LETTERBOX, ENVELOPE); + typeMap.put(EVENT, CALENDAR); + typeMap.put(CITO_EVENT, CALENDAR); + typeMap.put(MEGA_EVENT, CALENDAR); + typeMap.put(WHERIGO, COMPASS); + typeMap.put(WEBCAM_CACHE, CAMERA); + typeMap.put(VIRTUAL_CACHE, LAPTOP); + } + + public static Text getIconAsText(Type type, String iconSize) { + return createIcon(getIcon(type), iconSize); + } + + public static Text getIconAsText(Type type) { + return getIconAsText(type, GlyphIcon.DEFAULT_ICON_SIZE); + } + + public static String getIconAsString(Type type) { + return getIcon(type).characterToString(); + } + + public static GlyphIcons getIcon(Type type) { + GlyphIcons iconName = typeMap.get(type); + if (iconName == null) { + iconName = FontAwesomeIcons.BLANK; + } + return iconName; + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // star icon + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + public static Text getStars(String value) { + return getStarsAsText(value, GlyphIcon.DEFAULT_ICON_SIZE); + } + + public static String getStarsAsString(String value) { + StringBuffer stringBuffer = new StringBuffer(); + switch (value) { + case "1": + stringBuffer.append(STAR.characterToString()); + break; + + case "1.5": + stringBuffer.append(STAR.characterToString()) + .append(STAR_HALF.characterToString()); + break; + + case "2": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()); + break; + + case "2.5": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR_HALF.characterToString()); + break; + + case "3": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()); + break; + + case "3.5": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR_HALF.characterToString()); + break; + + case "4": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()); + break; + + case "4.5": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR_HALF.characterToString()); + break; + + case "5": + stringBuffer.append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()) + .append(STAR.characterToString()); + break; + + default: + stringBuffer.append(FontAwesomeIcons.BLANK.characterToString()); + System.out.println(value); + } + return stringBuffer.toString(); + } + + public static Text getStarsAsText(String value, String iconSize) { + Text text = createIcon(FontAwesomeIcons.BLANK, iconSize); + text.setText(value); + return text; + } + +} diff --git a/src/main/java/de/geofroggerfx/ui/details/DetailsController.java b/src/main/java/de/geofroggerfx/ui/details/DetailsController.java new file mode 100644 index 0000000..fcde0c4 --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/details/DetailsController.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui.details; + +import com.lynden.gmapsfx.GoogleMapView; +import com.lynden.gmapsfx.MapComponentInitializedListener; +import com.lynden.gmapsfx.javascript.object.*; +import de.geofroggerfx.application.SessionContext; +import de.geofroggerfx.application.SessionContextListener; +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.ui.FXMLController; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.URL; +import java.util.ResourceBundle; + +import static de.geofroggerfx.application.SessionContext.CURRENT_CACHE; +import static de.geofroggerfx.ui.GeocachingIcons.getStarsAsString; + +/** + * Created by Andreas on 09.03.2015. + */ +@Component +public class DetailsController extends FXMLController { + + @Autowired + private SessionContext sessionContext; + + @FXML + private Label cacheName; + + @FXML + private Text cacheDifficulty; + + @FXML + private Text cacheTerrain; + + @FXML + private VBox mainContent; + + @FXML + private WebView description; + + @FXML + private GoogleMapView mapView; + + private GoogleMap map; + private Marker marker; + + + @Value("${fxml.geofrogger.details.view}") + @Override + public void setFxmlFilePath(String filePath) { + this.fxmlFilePath = filePath; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + sessionContext.addListener(CURRENT_CACHE, new SessionContextListener() { + @Override + public void sessionContextChanged() { + fillContent(); + } + }); + + mapView.addMapInializedListener(new MapComponentInitializedListener() { + @Override + public void mapInitialized() { + //Set the initial properties of the map. + MapOptions mapOptions = new MapOptions(); + mapOptions.center(new LatLong(47.6097, -122.3331)) + .mapType(MapTypeIdEnum.ROADMAP) + .overviewMapControl(false) + .panControl(false) + .rotateControl(false) + .scaleControl(false) + .streetViewControl(false) + .zoomControl(false) + .zoom(12); + + map = mapView.createMap(mapOptions); + + LatLong latLong = new LatLong(49.045458, 9.189835); + MarkerOptions options = new MarkerOptions(); + options.position(latLong); + marker = new Marker(options); + map.addMarker(marker); + map.setCenter(latLong); + } + }); + } + + private void fillContent() { + Cache cache = (Cache) sessionContext.getData(CURRENT_CACHE); + cacheName.setText(cache.getName()); + cacheDifficulty.setText(getStarsAsString(cache.getDifficulty())); + cacheTerrain.setText(getStarsAsString(cache.getTerrain())); + + String shortDescription = cache.getShortDescription(); + if (!cache.getLongDescriptionHtml()) { + shortDescription = shortDescription.replaceAll("\n","
"); + } + + String longDescription = cache.getLongDescription(); + if (!cache.getLongDescriptionHtml()) { + longDescription = longDescription.replaceAll("\n","
"); + } + + String description = "

"+shortDescription+"

"+longDescription+"

"; + final WebEngine webEngine = this.description.getEngine(); + webEngine.loadContent(description); + + LatLong latLong = new LatLong(cache.getMainWayPoint().getLatitude(), cache.getMainWayPoint().getLongitude()); + MarkerOptions options = new MarkerOptions(); + options.position(latLong); + options.title(cache.getMainWayPoint().getName()); + + map.removeMarker(marker); + + marker.setTitle(cache.getMainWayPoint().getName()); + marker = new Marker(options); + map.addMarker(marker); + map.setCenter(latLong); + mapView.requestLayout(); + } +} diff --git a/src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcon.java b/src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcon.java new file mode 100644 index 0000000..85ca19a --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcon.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui.glyphs; + +import de.jensd.fx.glyphs.GlyphIcon; +import javafx.scene.text.Font; + +public class ElusiveIcon extends GlyphIcon { + + public final static String TTF_PATH = "/fonts/elusive/Elusive-Icons.ttf"; + public final static String FONT_FAMILY = "\'Elusive-Icons\'"; + + static { + Font.loadFont(ElusiveIcon.class.getResource(TTF_PATH).toExternalForm(), 10.0); + } + + @Override + public ElusiveIcons getDefaultGlyph() { + return ElusiveIcons.USER; + } + +} \ No newline at end of file diff --git a/src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcons.java b/src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcons.java new file mode 100644 index 0000000..3ab636b --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/glyphs/ElusiveIcons.java @@ -0,0 +1,359 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui.glyphs; + +import de.jensd.fx.glyphs.GlyphIcons; + +public enum ElusiveIcons implements GlyphIcons { + + ZOOM_OUT('\uE600'), + ZOOM_IN('\uE601'), + YOUTUBE('\uE602'), + WRENCH_ALT('\uE603'), + WRENCH('\uE604'), + WORDPRESS('\uE605'), + WHEELCHAIR('\uE606'), + WEBSITE_ALT('\uE607'), + WEBSITE('\uE608'), + WARNING_SIGN('\uE609'), + W3C('\uE60A'), + VOLUME_UP('\uE60B'), + VOLUME_OFF('\uE60C'), + VOLUME_DOWN('\uE60D'), + VKONTAKTE('\uE60E'), + VIMEO('\uE60F'), + VIEW_MODE('\uE610'), + VIDEO_CHAT('\uE611'), + VIDEO_ALT('\uE612'), + VIDEO('\uE613'), + VIADEO('\uE614'), + USER('\uE615'), + USD('\uE616'), + UPLOAD('\uE617'), + UNLOCK_ALT('\uE618'), + UNLOCK('\uE619'), + UNIVERSAL_ACCESS('\uE61A'), + TWITTER('\uE61B'), + TUMBLR('\uE61C'), + TRASH_ALT('\uE61D'), + TRASH('\uE61E'), + TORSO('\uE61F'), + TINT('\uE620'), + TIME_ALT('\uE621'), + TIME('\uE622'), + THUMBS_UP('\uE623'), + THUMBS_DOWN('\uE624'), + TH_LIST('\uE625'), + TH_LARGE('\uE626'), + TH('\uE627'), + TEXT_WIDTH('\uE628'), + TEXT_HEIGHT('\uE629'), + TASKS('\uE62A'), + TAGS('\uE62B'), + TAG('\uE62C'), + STUMBLEUPON('\uE62D'), + STOP_ALT('\uE62E'), + STOP('\uE62F'), + STEP_FORWARD('\uE630'), + STEP_BACKWARD('\uE631'), + STAR_EMPTY('\uE632'), + STAR_ALT('\uE633'), + STAR('\uE634'), + STACKOVERFLOW('\uE635'), + SPOTIFY('\uE636'), + SPEAKER('\uE637'), + SOUNDCLOUD('\uE638'), + SMILEY_ALT('\uE639'), + SMILEY('\uE63A'), + SLIDESHARE('\uE63B'), + SKYPE('\uE63C'), + SIGNAL('\uE63D'), + SHOPPING_CART_SIGN('\uE63E'), + SHOPPING_CART('\uE63F'), + SHARE_ALT('\uE640'), + SHARE('\uE641'), + SEARCH_ALT('\uE642'), + SEARCH('\uE643'), + SCREENSHOT('\uE644'), + SCREEN_ALT('\uE645'), + SCREEN('\uE646'), + SCISSORS('\uE647'), + RSS('\uE648'), + ROAD('\uE649'), + REVERSE_ALT('\uE64A'), + RETWEET('\uE64B'), + RETURN_KEY('\uE64C'), + RESIZE_VERTICAL('\uE64D'), + RESIZE_SMALL('\uE64E'), + RESIZE_HORIZONTAL('\uE64F'), + RESIZE_FULL('\uE650'), + REPEAT_ALT('\uE651'), + REPEAT('\uE652'), + REMOVE_SIGN('\uE653'), + REMOVE_CIRCLE('\uE654'), + REMOVE('\uE655'), + REFRESH('\uE656'), + REDDIT('\uE657'), + RECORD('\uE658'), + RANDOM('\uE659'), + QUOTES_ALT('\uE65A'), + QUOTES('\uE65B'), + QUESTION_SIGN('\uE65C'), + QUESTION('\uE65D'), + QRCODE('\uE65E'), + PUZZLE('\uE65F'), + PRINT('\uE660'), + PODCAST('\uE661'), + PLUS_SIGN('\uE662'), + PLUS('\uE663'), + PLAY_CIRCLE('\uE664'), + PLAY_ALT('\uE665'), + PLAY('\uE666'), + PLANE('\uE667'), + PINTEREST('\uE668'), + PICTURE('\uE669'), + PICASA('\uE66A'), + PHOTO_ALT('\uE66B'), + PHOTO('\uE66C'), + PHONE_ALT('\uE66D'), + PHONE('\uE66E'), + PERSON('\uE66F'), + PENCIL_ALT('\uE670'), + PENCIL('\uE671'), + PAUSE_ALT('\uE672'), + PAUSE('\uE673'), + PATH('\uE674'), + PAPER_CLIP_ALT('\uE675'), + PAPER_CLIP('\uE676'), + OPENSOURCE('\uE677'), + OK_SIGN('\uE678'), + OK_CIRCLE('\uE679'), + OK('\uE67A'), + OFF('\uE67B'), + NETWORK('\uE67C'), + MYSPACE('\uE67D'), + MUSIC('\uE67E'), + MOVE('\uE67F'), + MINUS_SIGN('\uE680'), + MINUS('\uE681'), + MIC_ALT('\uE682'), + MIC('\uE683'), + MAP_MARKER_ALT('\uE684'), + MAP_MARKER('\uE685'), + MALE('\uE686'), + MAGNET('\uE687'), + MAGIC('\uE688'), + LOCK_ALT('\uE689'), + LOCK('\uE68A'), + LIVEJOURNAL('\uE68B'), + LIST_ALT('\uE68C'), + LIST('\uE68D'), + LINKEDIN('\uE68E'), + LINK('\uE68F'), + LINES('\uE690'), + LEAF('\uE691'), + LASTFM('\uE692'), + LAPTOP_ALT('\uE693'), + LAPTOP('\uE694'), + KEY('\uE695'), + ITALIC('\uE696'), + IPHONE_HOME('\uE697'), + INSTAGRAM('\uE698'), + INFO_SIGN('\uE699'), + INDENT_RIGHT('\uE69A'), + INDENT_LEFT('\uE69B'), + INBOX_BOX('\uE69C'), + INBOX_ALT('\uE69D'), + INBOX('\uE69E'), + IDEA_ALT('\uE69F'), + IDEA('\uE6A0'), + HOURGLASS('\uE6A1'), + HOME_ALT('\uE6A2'), + HOME('\uE6A3'), + HEART_EMPTY('\uE6A4'), + HEART_ALT('\uE6A5'), + HEART('\uE6A6'), + HEARING_IMPAIRED('\uE6A7'), + HEADPHONES('\uE6A8'), + HDD('\uE6A9'), + HAND_UP('\uE6AA'), + HAND_RIGHT('\uE6AB'), + HAND_LEFT('\uE6AC'), + HAND_DOWN('\uE6AD'), + GUIDEDOG('\uE6AE'), + GROUP_ALT('\uE6AF'), + GROUP('\uE6B0'), + GRAPH_ALT('\uE6B1'), + GRAPH('\uE6B2'), + GOOGLEPLUS('\uE6B3'), + GLOBE_ALT('\uE6B4'), + GLOBE('\uE6B5'), + GLASSES('\uE6B6'), + GLASS('\uE6B7'), + GITHUB_TEXT('\uE6B8'), + GITHUB('\uE6B9'), + GIFT('\uE6BA'), + GBP('\uE6BB'), + FULLSCREEN('\uE6BC'), + FRIENDFEED_RECT('\uE6BD'), + FRIENDFEED('\uE6BE'), + FOURSQUARE('\uE6BF'), + FORWARD_ALT('\uE6C0'), + FORWARD('\uE6C1'), + FORK('\uE6C2'), + FONTSIZE('\uE6C3'), + FONT('\uE6C4'), + FOLDER_SIGN('\uE6C5'), + FOLDER_OPEN('\uE6C6'), + FOLDER_CLOSE('\uE6C7'), + FOLDER('\uE6C8'), + FLICKR('\uE6C9'), + FLAG_ALT('\uE6CA'), + FLAG('\uE6CB'), + FIRE('\uE6CC'), + FILTER('\uE6CD'), + FILM('\uE6CE'), + FILE_NEW_ALT('\uE6CF'), + FILE_NEW('\uE6D0'), + FILE_EDIT_ALT('\uE6D1'), + FILE_EDIT('\uE6D2'), + FILE_ALT('\uE6D3'), + FILE('\uE6D4'), + FEMALE('\uE6D5'), + FAST_FORWARD('\uE6D6'), + FAST_BACKWARD('\uE6D7'), + FACETIME_VIDEO('\uE6D8'), + FACEBOOK('\uE6D9'), + EYE_OPEN('\uE6DA'), + EYE_CLOSE('\uE6DB'), + EXCLAMATION_SIGN('\uE6DC'), + EUR('\uE6DD'), + ERROR_ALT('\uE6DE'), + ERROR('\uE6DF'), + ENVELOPE_ALT('\uE6E0'), + ENVELOPE('\uE6E1'), + EJECT('\uE6E2'), + EDIT('\uE6E3'), + DRIBBBLE('\uE6E4'), + DOWNLOAD_ALT('\uE6E5'), + DOWNLOAD('\uE6E6'), + DIGG('\uE6E7'), + DEVIANTART('\uE6E8'), + DELICIOUS('\uE6E9'), + DASHBOARD('\uE6EA'), + CSS('\uE6EB'), + CREDIT_CARD('\uE6EC'), + COMPASS_ALT('\uE6ED'), + COMPASS('\uE6EE'), + COMMENT_ALT('\uE6EF'), + COMMENT('\uE6F0'), + COGS('\uE6F1'), + COG_ALT('\uE6F2'), + COG('\uE6F3'), + CLOUD_ALT('\uE6F4'), + CLOUD('\uE6F5'), + CIRCLE_ARROW_UP('\uE6F6'), + CIRCLE_ARROW_RIGHT('\uE6F7'), + CIRCLE_ARROW_LEFT('\uE6F8'), + CIRCLE_ARROW_DOWN('\uE6F9'), + CHILD('\uE6FA'), + CHEVRON_UP('\uE6FB'), + CHEVRON_RIGHT('\uE6FC'), + CHEVRON_LEFT('\uE6FD'), + CHEVRON_DOWN('\uE6FE'), + CHECK_EMPTY('\uE6FF'), + CHECK('\uE700'), + CERTIFICATE('\uE701'), + CC('\uE702'), + CARET_UP('\uE703'), + CARET_RIGHT('\uE704'), + CARET_LEFT('\uE705'), + CARET_DOWN('\uE706'), + CAR('\uE707'), + CAMERA('\uE708'), + CALENDAR_SIGN('\uE709'), + CALENDAR('\uE70A'), + BULLHORN('\uE70B'), + BULB('\uE70C'), + BRUSH('\uE70D'), + BROOM('\uE70E'), + BRIEFCASE('\uE70F'), + BRAILLE('\uE710'), + BOOKMARK_EMPTY('\uE711'), + BOOKMARK('\uE712'), + BOOK('\uE713'), + BOLD('\uE714'), + BLOGGER('\uE715'), + BLIND('\uE716'), + BELL('\uE717'), + BEHANCE('\uE718'), + BARCODE('\uE719'), + BAN_CIRCLE('\uE71A'), + BACKWARD('\uE71B'), + ASL('\uE71C'), + ARROW_UP('\uE71D'), + ARROW_RIGHT('\uE71E'), + ARROW_LEFT('\uE71F'), + ARROW_DOWN('\uE720'), + ALIGN_RIGHT('\uE721'), + ALIGN_LEFT('\uE722'), + ALIGN_JUSTIFY('\uE723'), + ALIGN_CENTER('\uE724'), + ADULT('\uE725'), + ADJUST_ALT('\uE726'), + ADJUST('\uE727'), + ADDRESS_BOOK_ALT('\uE728'), + ADDRESS_BOOK('\uE729'), + ASTERISK('\uE72A'); + + private final char character; + + private ElusiveIcons(char character) { + this.character = character; + } + + + @Override + public char getChar() { + return character; + } + + @Override + public String unicodeToString() { + return String.format("\\u%04x", (int) character); + } + + @Override + public String characterToString() { + return Character.toString(character); + } + + @Override + public String getFontFamily() { + return ElusiveIcon.FONT_FAMILY; + } + +} diff --git a/src/main/java/de/geofroggerfx/ui/glyphs/GeofroggerGlyphsDude.java b/src/main/java/de/geofroggerfx/ui/glyphs/GeofroggerGlyphsDude.java new file mode 100644 index 0000000..9e0c45f --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/glyphs/GeofroggerGlyphsDude.java @@ -0,0 +1,14 @@ +package de.geofroggerfx.ui.glyphs; + +import de.jensd.fx.glyphs.GlyphsDude; +import javafx.scene.text.Font; + +/** + * Created by Andreas on 23.03.2015. + */ +public class GeofroggerGlyphsDude extends GlyphsDude { + + static { + Font.loadFont(GeofroggerGlyphsDude.class.getResource(ElusiveIcon.TTF_PATH).toExternalForm(), 10.0); + } +} diff --git a/src/main/java/de/geofroggerfx/ui/list/CacheListCell.java b/src/main/java/de/geofroggerfx/ui/list/CacheListCell.java new file mode 100644 index 0000000..b01da0c --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/list/CacheListCell.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui.list; + +import de.geofroggerfx.model.CacheListEntry; +import de.geofroggerfx.ui.GeocachingIcons; +import de.jensd.fx.glyphs.GlyphsDude; +import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcons; +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.text.Text; + + +/** + * Multi-Column-Row list cell to shows the most important data in a list. + * + * @author Andreas + */ +public class CacheListCell extends ListCell { + private static final String YELLOW = ";-fx-fill: linear-gradient(#e8e474 0%, #edcf59 70%, #bfba26 85%);"; + private static final String GRAY = ";-fx-fill: linear-gradient(#cccccc 0%, #999999 70%, #888888 85%);"; + private final GridPane grid = new GridPane(); + private final Text icon = GlyphsDude.createIcon(FontAwesomeIcons.BLANK, "20.0"); + private final Label name = new Label(); + private final Text difficultyStars = GlyphsDude.createIcon(FontAwesomeIcons.BLANK, "8.0"); + private final Text terrainStars = GlyphsDude.createIcon(FontAwesomeIcons.BLANK, "8.0"); + + public CacheListCell() { + configureGrid(); + configureIcon(); + configureName(); + configureDifficulty(); + configureTerrain(); + addControlsToGrid(); + } + + @Override + public void updateItem(CacheListEntry cache, boolean empty) { + super.updateItem(cache, empty); + if (empty) { + clearContent(); + } else { + addContent(cache); + } + } + + private void configureGrid() { + grid.setHgap(10); + grid.setVgap(4); + grid.setPadding(new Insets(0, 10, 0, 10)); + + ColumnConstraints column1 = new ColumnConstraints(32); + ColumnConstraints column2 = new ColumnConstraints(USE_COMPUTED_SIZE , USE_COMPUTED_SIZE, Double.MAX_VALUE); + column2.setHgrow(Priority.NEVER); + ColumnConstraints column3 = new ColumnConstraints(30 , 50 , Double.MAX_VALUE); + column3.setHgrow(Priority.ALWAYS); + column3.setFillWidth(true); + ColumnConstraints column4 = new ColumnConstraints(USE_COMPUTED_SIZE , USE_COMPUTED_SIZE , Double.MAX_VALUE); + column4.setHgrow(Priority.NEVER); + ColumnConstraints column5 = new ColumnConstraints(30 , 50 , Double.MAX_VALUE); + column5.setHgrow(Priority.ALWAYS); + column5.setFillWidth(true); + grid.getColumnConstraints().addAll(column1, column2, column3, column4, column5); + } + + private void configureIcon() { + icon.setStyle(icon.getStyle()+ GRAY); + } + + private void configureName() { + name.setStyle("-fx-font-weight: bold;"); + } + + private void configureDifficulty() { + difficultyStars.setStyle(difficultyStars.getStyle() + YELLOW); + } + + private void configureTerrain() { + terrainStars.setStyle(difficultyStars.getStyle() + YELLOW); + } + + + private void addControlsToGrid() { + grid.add(icon, 0, 0, 1, 2); + grid.add(name, 1, 0, 4, 1); + grid.add(new Label("Difficulty:"), 1, 1); + grid.add(difficultyStars, 2, 1); + grid.add(new Label("Terrain:"), 3, 1); + grid.add(terrainStars, 4, 1); + } + + private void clearContent() { + setText(null); + setGraphic(null); + } + + private void addContent(CacheListEntry cache) { + setIcon(cache); + setCacheName(cache); + setDifficulty(cache); + setTerrain(cache); + setGraphic(grid); + } + + private void setCacheName(CacheListEntry cache) { + name.setText(cache.getName()); + } + + private void setIcon(CacheListEntry cache) { + icon.setText(GeocachingIcons.getIconAsString(cache.getType())); + } + + private void setDifficulty(CacheListEntry cache) { + difficultyStars.setText(GeocachingIcons.getStarsAsString(cache.getDifficulty())); + } + + private void setTerrain(CacheListEntry cache) { + terrainStars.setText(GeocachingIcons.getStarsAsString(cache.getTerrain())); + } + +} diff --git a/src/main/java/de/geofroggerfx/ui/list/ListController.java b/src/main/java/de/geofroggerfx/ui/list/ListController.java new file mode 100644 index 0000000..1b03fb5 --- /dev/null +++ b/src/main/java/de/geofroggerfx/ui/list/ListController.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) Andreas Billmann + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 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 HOLDER 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 de.geofroggerfx.ui.list; + +import de.geofroggerfx.application.SessionContext; +import de.geofroggerfx.model.Cache; +import de.geofroggerfx.model.CacheListEntry; +import de.geofroggerfx.service.CacheService; +import de.geofroggerfx.ui.FXMLController; +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; + +import static de.geofroggerfx.application.SessionContext.CACHE_ENTRY_LIST; +import static de.geofroggerfx.application.SessionContext.CURRENT_CACHE; + +/** + * Created by Andreas on 09.03.2015. + */ +@Component +public class ListController extends FXMLController { + + @Autowired + private SessionContext sessionContext; + + @Autowired + private CacheService cacheService; + + @FXML + ListView cacheListView; + + @Value("${fxml.geofrogger.list.view}") + @Override + public void setFxmlFilePath(String filePath) { + this.fxmlFilePath = filePath; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + setCellFactory(); + sessionContext.addListener(CACHE_ENTRY_LIST, () -> Platform.runLater(this::resetCacheList)); + cacheListView.getSelectionModel().selectedItemProperty().addListener( + (InvalidationListener) observable -> setSelectedCacheIntoSession() + ); + } + + private void resetCacheList() { + List caches = (List) sessionContext.getData(CACHE_ENTRY_LIST); + cacheListView.getItems().setAll(caches); + } + + @SuppressWarnings("unchecked") + private void setCellFactory() { + cacheListView.setCellFactory(param -> new CacheListCell()); + } + + private void setSelectedCacheIntoSession() { + CacheListEntry entry = (CacheListEntry) cacheListView.getSelectionModel().getSelectedItem(); + Cache cache = null; + if (entry != null) { + cache = cacheService.getCacheForId(entry.getId()); + } + sessionContext.setData(CURRENT_CACHE, cache); + } +} diff --git a/src/main/resources/de/geofroggerfx/dao/jdbc/database.properties b/src/main/resources/de/geofroggerfx/dao/jdbc/database.properties new file mode 100644 index 0000000..933590a --- /dev/null +++ b/src/main/resources/de/geofroggerfx/dao/jdbc/database.properties @@ -0,0 +1,3 @@ +jdbc.url=jdbc:h2:file:C:/billmann/projects/GeoFroggerFX2/geofroggerfx +jdbc.username = sa +jdbc.password = sa \ No newline at end of file diff --git a/src/main/resources/de/geofroggerfx/dao/jdbc/updates/1.sql b/src/main/resources/de/geofroggerfx/dao/jdbc/updates/1.sql new file mode 100644 index 0000000..e7a424b --- /dev/null +++ b/src/main/resources/de/geofroggerfx/dao/jdbc/updates/1.sql @@ -0,0 +1,40 @@ +CREATE TABLE cache ( + id INT PRIMARY KEY, + name VARCHAR (255), + placed_by VARCHAR (255), + available BOOLEAN, + archived BOOLEAN, + found BOOLEAN, + difficulty VARCHAR (255), + terrain VARCHAR (255), + country VARCHAR (255), + state VARCHAR (255), + short_description VARCHAR(2147483647), + short_description_html BOOLEAN, + long_description VARCHAR(2147483647), + long_description_html BOOLEAN, + encoded_hints VARCHAR(2147483647), + container VARCHAR (255), + type VARCHAR (255), + user_id INT +); + +CREATE TABLE waypoint ( + name VARCHAR (255) PRIMARY KEY , + cache_id INT, + latitude DOUBLE, + longitude DOUBLE, + time TIMESTAMP, + description VARCHAR (255), + url VARCHAR (255), + urlName VARCHAR (255), + symbol VARCHAR (255), + type VARCHAR (255), + FOREIGN KEY(cache_id) REFERENCES cache(id) ON DELETE CASCADE +); + +CREATE TABLE version ( + version INT +); + +INSERT INTO version(version) values(1); \ No newline at end of file diff --git a/src/main/resources/de/geofroggerfx/ui/application.properties b/src/main/resources/de/geofroggerfx/ui/application.properties new file mode 100644 index 0000000..3426da8 --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/application.properties @@ -0,0 +1,10 @@ +application.name = GeoFroggerFX +application.version = 2.0 + +# fxml views +fxml.geofrogger.view = /de/geofroggerfx/ui/geofroggerfx.fxml +fxml.geofrogger.list.view = /de/geofroggerfx/ui/list/list.fxml +fxml.geofrogger.details.view = /de/geofroggerfx/ui/details/details.fxml + +# resources +resource.main = de.geofroggerfx.ui.geofrogger \ No newline at end of file diff --git a/src/main/resources/de/geofroggerfx/ui/details/details.fxml b/src/main/resources/de/geofroggerfx/ui/details/details.fxml new file mode 100644 index 0000000..fbc1c99 --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/details/details.fxml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + +
diff --git a/src/main/resources/de/geofroggerfx/ui/geofrogger_de.properties b/src/main/resources/de/geofroggerfx/ui/geofrogger_de.properties new file mode 100644 index 0000000..abd2741 --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/geofrogger_de.properties @@ -0,0 +1,52 @@ +menu.title.import.gpx = Importiere GPX +menu.title.file = Datei +menu.title.quit = Beenden +menu.title.help = Hilfe +menu.title.settings = Einstellungen +menu.title.about = \u00dcber GeoFroggerFX +menu.title.plugins = Plugins +menu.title.list = Listen +menu.title.list.new = Liste anlegen +menu.title.list.delete = Liste l\u00F6schen + +menu.title.sort = Sortieren +menu.title.filter = Filtern + +label.text.cache.list=Liste: +label.text.cache.list.count=Anzahl: +label.text.name=Name: +label.text.difficulty=Schwierigkeit: +label.text.terrain=Gel\u00E4nde +label.text.placedBy=Platziert von: +label.text.owner=Betreut von: +label.text.date=Datum: +label.text.type=Typ: +label.text.container=Gr\u00F6\u00dfe: +label.text.shortdescription=Kurze Beschreibung: +label.text.htmldescription=HTML Beschreibung +label.text.longdescription=Lange Beschreibung: + +tab.text.descriptions=Beschreibungen +tab.text.general=Allgemein + +sort.cache.name = GC Code +sort.cache.type = Art +sort.cache.difficulty = Schwierigkeitsgrad +sort.cache.terrain = Gel\u00E4nde +sort.cache.placedBy = Platziert von +sort.cache.owner = Betreut von + +dialog.title.about = Über +dialog.title.new_list = Neue Liste +dialog.label.listname = Name der Liste: +dialog.msg.list.does.exist = Diese Liste existiert schon! +dialog.title.delete_list = Liste löschen + +all.caches = Alle Caches + +status.load.all.caches.from.db = Lade alle Caches von der Datenbank. +status.all.cache.lists.loaded = Alle Listen geladen. +status.load.caches.from.db = Lade Caches von der Datenbank. +status.all.caches.loaded = Alle Caches geladen. +status.store.all.caches = Speichere Caches in Datenbank. +status.all.caches.stored = Alle Caches in der Datenbank gespeichert. \ No newline at end of file diff --git a/src/main/resources/de/geofroggerfx/ui/geofrogger_en.properties b/src/main/resources/de/geofroggerfx/ui/geofrogger_en.properties new file mode 100644 index 0000000..41802fb --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/geofrogger_en.properties @@ -0,0 +1,53 @@ +menu.title.import.gpx = Import GPX +menu.title.file = File +menu.title.quit = Quit +menu.title.help = Help +menu.title.settings = Settings +menu.title.about = About GeoFroggerFX +menu.title.plugins = Plugins +menu.title.list = Lists +menu.title.list.new = New list +menu.title.list.delete = Delete list + + +menu.title.sort = Sort +menu.title.filter = Filter + +label.text.cache.list=List: +label.text.cache.list.count=Number: +label.text.name=Name: +label.text.difficulty=Difficulty: +label.text.terrain=Terrain: +label.text.placedBy=Placed By: +label.text.owner=Owner: +label.text.date=Date: +label.text.type=Type: +label.text.container=Container: +label.text.shortdescription=Short description: +label.text.htmldescription=HTML Description +label.text.longdescription=Long description: + +tab.text.descriptions=Descriptions +tab.text.general=General + +sort.cache.name = GC Code +sort.cache.type = Type +sort.cache.difficulty = Difficulty +sort.cache.terrain = Terrain +sort.cache.placedBy = Placed by +sort.cache.owner = Owner + +dialog.title.about = About +dialog.title.new_list = New list +dialog.label.listname = Name of list: +dialog.msg.list.does.exist = List does already exist! +dialog.title.delete_list = Delete list + +all.caches = All caches + +status.load.all.caches.from.db = Load cache lists from database. +status.all.cache.lists.loaded = All cache lists loaded. +status.load.caches.from.db = Load caches from database. +status.all.caches.loaded = All caches loaded. +status.store.all.caches = Store caches in database. +status.all.caches.stored = All caches are stored in database. \ No newline at end of file diff --git a/src/main/resources/de/geofroggerfx/ui/geofroggerfx.css b/src/main/resources/de/geofroggerfx/ui/geofroggerfx.css new file mode 100644 index 0000000..ad78d49 --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/geofroggerfx.css @@ -0,0 +1,4 @@ +.cache-h1 { + -fx-font-size: 24pt; + -fx-font-family: "Sofia-Regular"; +} diff --git a/src/main/resources/de/geofroggerfx/ui/geofroggerfx.fxml b/src/main/resources/de/geofroggerfx/ui/geofroggerfx.fxml new file mode 100644 index 0000000..e508715 --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/geofroggerfx.fxml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ + + + + + + + + + + + + + + diff --git a/src/main/resources/de/geofroggerfx/ui/list/list.fxml b/src/main/resources/de/geofroggerfx/ui/list/list.fxml new file mode 100644 index 0000000..00a1630 --- /dev/null +++ b/src/main/resources/de/geofroggerfx/ui/list/list.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fonts/elusive/Elusive-Icons.ttf b/src/main/resources/fonts/elusive/Elusive-Icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ba2bafe7fedb41297e41bac3c348bc81c693658f GIT binary patch literal 61640 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/fonts/elusive/license.txt b/src/main/resources/fonts/elusive/license.txt new file mode 100644 index 0000000..9f139b3 --- /dev/null +++ b/src/main/resources/fonts/elusive/license.txt @@ -0,0 +1,86 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/src/main/resources/fonts/elusive/readme.txt b/src/main/resources/fonts/elusive/readme.txt new file mode 100644 index 0000000..6a2ed90 --- /dev/null +++ b/src/main/resources/fonts/elusive/readme.txt @@ -0,0 +1,10 @@ +Elusive-Iconfont +================ + +The Elusive Iconfont is an icons webfont, optimized for use with twitter's bootstrap. + +For examples & usage see http://shoestrap.org/downloads/elusive-icons-webfont/ + +Licence: [SIL](http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) + +https://github.com/reduxframework/elusive-iconfont \ No newline at end of file diff --git a/src/main/resources/fonts/sofia/SIL OFL Font License Sofia.txt b/src/main/resources/fonts/sofia/SIL OFL Font License Sofia.txt new file mode 100644 index 0000000..bdeef80 --- /dev/null +++ b/src/main/resources/fonts/sofia/SIL OFL Font License Sofia.txt @@ -0,0 +1,94 @@ +Copyright (c) 2011 by LatinoType Limitada (luciano@latinotype.com), +with Reserved Font Names "Sofia" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/fonts/sofia/Sofia-Regular.otf b/src/main/resources/fonts/sofia/Sofia-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..1f76d2d5819c6ba94fcc1dcc9f0f32c941121d07 GIT binary patch literal 23768 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