100 Commits

Author SHA1 Message Date
8c6a43fc22 change version to 1.0 2019-06-13 22:21:41 +02:00
7f4182bfad added JFXInfoAlert if connect manually failed
closes #39
2019-06-13 21:52:26 +02:00
5fff4023f9 clear DetailVIew on delete, rename submitButton to btnSubmit, clean up 2019-06-13 21:43:43 +02:00
dfad679e6e updated planner 2019-06-13 21:36:14 +02:00
391dfe4282 don't open export if nothing is selected & clean up 2019-06-13 21:22:42 +02:00
225316abb9 Merge branch 'fix-40' of localhorst/TextureSync into master 2019-06-13 20:04:44 +02:00
f5dbcc0134 use Borderpane + Anchorpane for #40 2019-06-13 18:38:38 +02:00
cac4807357 BUG FIX: show resolution in detailview. 2019-06-13 18:37:07 +02:00
33fa741a2a fix font-color of import action btn 2019-06-13 18:35:34 +02:00
37406acd98 Fix #27 (StartupViewController is created twice) again 2019-06-13 18:04:46 +02:00
56c62422cc use blue for "Ändern" Button 2019-06-13 00:19:16 +02:00
bc43b33536 Fix #38
Note: The client architecture is broken beyond repair
2019-06-13 00:10:00 +02:00
1ac13f80d5 fix MainView ChipView TextColor 2019-06-12 22:54:30 +02:00
8dfbbae559 fix #28 2019-06-12 22:42:28 +02:00
cc03b32ade Fix #29
Redo Text of FolderView + DetailView
2019-06-12 21:05:40 +02:00
a2f5b65e30 Calling App.start(stage) early
Calling super.start early prevents a weird layouting bug
were the button of the window is white.
This could(?) be caused by stage and StartupView having
a different min-size.
2019-06-12 19:37:32 +02:00
f094a68b04 Merge branch 'resize' 2019-06-12 19:11:08 +02:00
b5545e1bae fix icon path: use png for alpha-channel 2019-06-12 19:07:29 +02:00
9a9a1e0056 Merge commit '10076fc' into resize 2019-06-12 19:03:26 +02:00
0f23749001 Merge commit '6219ad9' into resize 2019-06-12 19:03:19 +02:00
c1dbd8ffb7 Merge commit '35f8145' into resize 2019-06-12 19:03:05 +02:00
ebc6449e48 Merge commit 'e26d54e' into resize 2019-06-12 19:02:50 +02:00
bef56f978c Merge commit '9975364' into resize 2019-06-12 19:02:12 +02:00
50ea54c8d6 reformat 2019-06-12 18:57:42 +02:00
24b9755089 Merge commit '4915788' into resize 2019-06-12 18:56:37 +02:00
6fbfa050b3 Merge commit 'ee7fbae' into resize 2019-06-12 18:53:57 +02:00
9a3524143a Merge commit 'efc2845' into resize 2019-06-12 18:53:42 +02:00
10076fcef2 updated test docs 2019-06-12 14:08:23 +02:00
6219ad93d0 Fix #31 : Config Path use ~/.config instead.
This is more common for Desktop Apps.
2019-06-12 13:28:03 +02:00
8109782e4e Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-12 13:24:26 +02:00
c2d01760c7 Set Server Version to 1.0; Show Version on Startup 2019-06-12 13:23:47 +02:00
2cac52b48d Replace logo with the new one 2019-06-12 11:38:05 +02:00
35f8145112 [kurzanleitung] added first thoughts 2019-06-10 16:05:07 +02:00
e26d54e96f added praesentation 2019-06-10 15:58:32 +02:00
7e3ce9ce56 fixed StartupViewController is created twice
closes #27
2019-06-10 15:30:00 +02:00
9975364621 updated all test documents 2019-06-10 15:19:53 +02:00
491578806e Multiple changes in the ChipView will now work for the update to server 2019-06-10 14:05:56 +02:00
b1052fa894 added shortcut ctrl+e for exporting a texture 2019-06-10 12:41:11 +02:00
36c4805061 added shortcut ctrl+i for the ImportView 2019-06-10 12:25:06 +02:00
ee7fbae29c Fix display of the month at creation date
This time on serialization as well as on deserialization
2019-06-09 16:29:37 +02:00
07f2682a2d resize ImportView too 2019-06-09 13:38:11 +02:00
efc28455c1 fixed error handling at import 2019-06-09 13:29:07 +02:00
5d223ba72b make window resizable, rework MainView code 2019-06-09 13:12:56 +02:00
15cbbb1bac fast fix for calendar bug 2019-06-09 13:03:20 +02:00
08840c4c2b Revert "Fix display of the month at creation date"
This reverts commit 19eb6660e1.
2019-06-09 13:02:26 +02:00
6b6ef67122 Revert "Make Client resizeable"
This reverts commit 36a8bccfe3.
2019-06-08 22:46:34 +02:00
4a213afdf5 Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-08 22:17:24 +02:00
36a8bccfe3 Make Client resizeable 2019-06-08 22:16:47 +02:00
4f35293151 hide metadata and ChipView in DetailView 2019-06-08 22:09:43 +02:00
29354199cc show all textures at startup 2019-06-08 21:59:09 +02:00
f4db466b37 Client Connection: [Auto]Retry Once
if Connection is lost or timed out
2019-06-08 21:25:56 +02:00
19eb6660e1 Fix display of the month at creation date 2019-06-08 16:35:45 +02:00
4e8b4c364b Add java program to create test data 2019-06-08 16:07:55 +02:00
f22a9eb260 added com.github.johnrengelman.shadow
this makes it a lot easier to build a runnable jar file
2019-06-08 15:32:05 +02:00
9ae50df1a4 don't resize the window, set the correct Title
* closes #19
2019-06-08 14:39:18 +02:00
ce11d2e263 added a icon 2019-06-08 14:27:11 +02:00
3f18760a74 Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-08 14:21:34 +02:00
89ef736954 alert if import failed 2019-06-08 14:20:48 +02:00
3e1c46db81 some clean up, adjusted the DetailView background 2019-06-08 14:19:14 +02:00
be11f35159 show alert if export fails 2019-06-08 14:01:49 +02:00
39c358c00f update to JFoenix 8.0.9
* added a promptText to the search ChipView
2019-06-08 13:48:16 +02:00
61980905e3 changed debbuging color in 3DPreview to release color 2019-06-08 13:40:59 +02:00
fc5db5d051 Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-08 13:32:09 +02:00
4c2e1c2917 show metadata in better format 2019-06-08 13:31:29 +02:00
a82cef599c GUI clean up
* moved some style code to styles.css
* adjust the background colors of the MainView
2019-06-08 13:25:55 +02:00
86008828f5 Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-08 13:14:56 +02:00
c1ec000b98 document autoconnect 2019-06-08 13:14:24 +02:00
ed558106e5 update texture to server when tags are changed 2019-06-08 12:47:34 +02:00
da153091bd highlight the selected element, closes #9 2019-06-08 12:25:20 +02:00
9200e9277a fixed app doesn't exit on close, added JFXInfoAlert
* closes #17
* closes #17
2019-06-08 12:09:40 +02:00
40bfc73ba8 import btn bigger font size 2019-06-07 22:05:24 +02:00
b1984ac8dd delete dialog btn text changed, ui fixes import view 2019-06-07 21:39:26 +02:00
1c1f12ecaf clean up 2019-06-07 16:57:15 +02:00
d5b7fd0f94 don't wait with adding untill all elements have been loaded 2019-06-07 16:19:04 +02:00
2c9a23ec8a some clean up 2019-06-07 14:01:16 +02:00
f45d807536 last import dir is now stored and used for next import 2019-06-07 13:49:40 +02:00
ed344cbd49 remove deleted Elements from the FolderView, comments 2019-06-07 13:48:42 +02:00
8e7ba1cb46 added back button in importview 2019-06-07 13:25:33 +02:00
b2a84db5a7 display stored address in ui 2019-06-07 10:00:03 +02:00
71f50b832e added autoconnect at startup 2019-06-07 09:52:09 +02:00
0f8d2ba256 added autoconnect to statusbericht 2019-06-06 22:42:10 +02:00
b0a1844b16 added statusbericht kw23 2019-06-06 22:25:22 +02:00
594f083a8d added JFXOkayCancelAlert 2019-06-06 14:41:30 +02:00
90b21ca3d1 added delte context item and call for server delete 2019-06-05 22:33:37 +02:00
db8178f4c4 export texture file to local filesystem 2019-06-05 22:07:03 +02:00
4cd4481182 change Semantics of Client AutoConnect.searchServer.
* now returns an optional
* uses multiple trys.
2019-06-05 19:43:11 +02:00
3c2984a404 fix formatting 2019-06-05 19:35:10 +02:00
c55182d6cc fix warnings in server code 2019-06-05 19:34:37 +02:00
e540e58161 forgot the checkin main, protocol/mod. Sry!! 2019-06-05 19:32:23 +02:00
727558d867 Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-05 17:06:01 +02:00
adc8725a36 Implement AutoConnect 2019-06-05 17:05:34 +02:00
99ef2a8edc Fix: Some Unknown Server Compile Problem. 2019-06-05 17:04:54 +02:00
de47e99852 clean up SettingsController
* don't use ";"
* use proper kotlin syntax
* make all switch View calls use runLater
2019-06-05 16:31:10 +02:00
d7a67c497e added import Button 2019-06-05 16:23:45 +02:00
8d387250e7 Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-05 16:06:06 +02:00
e32766e581 save and load server address 2019-06-05 16:05:10 +02:00
53dcebecde correctly placed the spinnerSearch, adjusted FolderView hGap and vGap 2019-06-05 15:44:12 +02:00
c80233155f fixed cvSearch style not applying 2019-06-04 20:49:07 +02:00
857f35e77e Merge branch 'master' of git.mosad.xyz:localhorst/TextureSync 2019-06-04 18:21:13 +02:00
e78e7e3a59 query images and show result 2019-06-04 18:20:09 +02:00
62 changed files with 85051 additions and 2867 deletions

View File

@ -15,21 +15,8 @@ buildscript {
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}
group 'com.hso'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1"
implementation "no.tornado:tornadofx:$tornadofx_version"
implementation "com.jfoenix:jfoenix:8.0.8"
implementation 'com.google.code.gson:gson:2.8.5'
id "com.github.johnrengelman.shadow" version "5.0.0"
id "application"
}
compileKotlin {
@ -38,3 +25,19 @@ compileKotlin {
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
group 'org.hso'
version '1.0'
archivesBaseName = 'TextureSync'
mainClassName = 'org.hso.texturesyncclient.app.Main'
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "no.tornado:tornadofx:$tornadofx_version"
implementation "com.jfoenix:jfoenix:8.0.9"
implementation 'com.google.code.gson:gson:2.8.5'
}

View File

@ -1,2 +1,2 @@
rootProject.name = 'texuresync_client'
rootProject.name = 'texturesync_client'

View File

@ -0,0 +1,39 @@
package org.hso.texturesyncclient.alerts
import com.jfoenix.controls.JFXDialogLayout
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXAlert
import javafx.event.ActionEvent
import javafx.scene.text.Text
import tornadofx.FX
/**
* Creates a new JFoenix Alert to show some information
* @param heading Heading text of the alert
* @param body Content text of the alert
* @param btnStyle Style of the okay button
*/
class JFXInfoAlert(heading: String, body: String, private var btnStyle: String) {
private var headingText = Text(heading)
private var bodyText = Text(body)
fun showAndWait() {
val alert = JFXAlert<Void>(FX.primaryStage)
val button = JFXButton("Okay")
button.addEventHandler(ActionEvent.ACTION) { alert.close() }
button.buttonType = JFXButton.ButtonType.RAISED
button.prefHeight = 32.0
button.style = btnStyle
val content = JFXDialogLayout()
content.setActions(button)
content.setHeading(headingText)
content.setBody(bodyText)
alert.setContent(content)
alert.showAndWait()
}
}

View File

@ -0,0 +1,58 @@
package org.hso.texturesyncclient.alerts
import com.jfoenix.controls.JFXAlert
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXDialogLayout
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.text.Text
import tornadofx.FX
/**
* @param heading Heading text of the alert
* @param body Content text of the alert
* @param btnStyle Style of the buttons
*/
class JFXOkayCancelAlert(heading: String, body: String, private var btnStyle: String) {
var okayAction: EventHandler<ActionEvent>? = null
var cancelAction: EventHandler<ActionEvent>? = null
private var okayText = "Löschen"
private var cancelText = "Abbrechen"
private var headingText = Text(heading)
private var bodyText = Text(body)
init {
//headingText.style = "-fx-font: 14px Verdana; -fx-text-fill: #2b7bbb;"
//bodyText.style = "-fx-font: 14px Verdana; -fx-text-fill: #2b7bbb;"
}
fun showAndWait() {
val alert = JFXAlert<Void>(FX.primaryStage)
val okayBtn = JFXButton(okayText)
okayBtn.addEventHandler(ActionEvent.ACTION) { alert.close() }
okayBtn.addEventHandler(ActionEvent.ACTION, okayAction!!)
okayBtn.buttonType = JFXButton.ButtonType.RAISED
okayBtn.prefHeight = 32.0
okayBtn.style = btnStyle
val cancelBtn = JFXButton(cancelText)
cancelBtn.addEventHandler(ActionEvent.ACTION) { alert.close() }
cancelBtn.addEventHandler(ActionEvent.ACTION, cancelAction!!)
cancelBtn.buttonType = JFXButton.ButtonType.RAISED
cancelBtn.prefHeight = 32.0
cancelBtn.style = btnStyle
val content = JFXDialogLayout()
//content.background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
content.setActions(cancelBtn, okayBtn)
content.setHeading(headingText)
content.setBody(bodyText)
alert.setContent(content)
alert.showAndWait()
}
}

View File

@ -1,17 +1,36 @@
package org.hso.texturesyncclient.app
import org.hso.texturesyncclient.controller.RootController
import org.hso.texturesyncclient.view.importView.ImportView
import org.hso.texturesyncclient.view.mainView.MainView
import javafx.scene.image.Image
import javafx.stage.Stage
import org.hso.texturesyncclient.controller.SettingsController
import org.hso.texturesyncclient.view.startupView.StartupView
import org.hso.texturesyncclient.view.startupView.StartupViewController
import tornadofx.App
class Main : App(StartupView::class) {
val controller = RootController()
private val svc: StartupViewController by inject()
init {
// TODO get saved IP address, if found try to connect, else show StartupView
override fun start(stage: Stage) {
// Calling super.start early prevents a weird layouting bug
// were the button of the window is white.
// This could(?) be caused by stage and StartupView having
// a different min-size.
super.start(stage)
stage.minWidth = 1050.00
stage.minHeight = 700.00
stage.width = 1050.00
stage.height = 700.00
stage.isResizable = true
stage.icons.add(Image("icons/TextureSync_Icon_256x256.png"))
stage.setOnCloseRequest { System.exit(0) }
stage.scene.stylesheets.add("/css/Styles.css")
SettingsController.init()
svc.initConnection()
}
}

View File

@ -1,102 +0,0 @@
package org.hso.texturesyncclient.controller
import javafx.collections.ObservableList
import org.hso.texturesyncclient.controller.net.Connection
import org.hso.texturesyncclient.model.Sha256
import org.hso.texturesyncclient.model.Texture
import org.hso.texturesyncclient.model.TextureFormat
import org.hso.texturesyncclient.view.importView.ImportViewController
import org.hso.texturesyncclient.view.mainView.MainView
import org.hso.texturesyncclient.view.mainView.MainViewController
import org.hso.texturesyncclient.view.startupView.StartupView
import org.hso.texturesyncclient.view.startupView.StartupViewController
import tornadofx.Controller
import java.net.InetAddress
import java.util.Calendar
import java.io.File
import javax.imageio.ImageIO
import java.util.UUID
import java.nio.file.Files
class RootController : Controller() {
private val mvc: MainViewController by inject()
private val svc: StartupViewController by inject()
private val ivc: ImportViewController by inject()
private lateinit var con: Connection
init {
/*var data = Texture()
var img = con.getTexturePreview(data.textureHash)
var test = GUIModel(data, img)
test.exportItem.setOnAction {
}
mvc.addElement(test)
data = Texture()
img = con.getTexturePreview(data.textureHash)
test = GUIModel(data, img)
test.exportItem.setOnAction {
}
mvc.addElement(test)*/
}
/**
* calculate the resolution, get today's date -> upload to server
* @param path the absolute path of the file on the client's system
* @param name the file name
* @param tags all tags for the file
*/
fun importTexture(path: String, name: String, tags: ObservableList<String>) {
val data = Files.readAllBytes(File(path).toPath()) // this is the image as byte array
val uuid = UUID.randomUUID()
val format = if (File(path).extension.toLowerCase() == "png") TextureFormat.PNG else TextureFormat.JPEG
val bimg = ImageIO.read(File(path)) //image for obtaining resolution
val resolution = Pair(bimg.height, bimg.width)
val cal = Calendar.getInstance() //calendar obj with current time
val hash = Sha256(data)
//Todo free image
val newTexture = Texture(uuid, name, tags.toTypedArray(), format, resolution, cal, hash)
try {
con.uploadTexture(newTexture, data)
println("Texture upload successful")
} catch (e: Exception) {
println(e)
}
}
/**
* Initialize connection to server
* @param name server name as IP or domain
*/
fun initConnection(name: String) {
try {
con = Connection(InetAddress.getByName(name))
println("ausgabe")
con.ping()
println("Connection successful")
// TODO store server ip for next start
// switch to MainView
find(StartupView::class).replaceWith(MainView::class, sizeToScene = true, centerOnScreen = true)
} catch (e: Exception) {
println(e)
}
}
}

View File

@ -1,8 +0,0 @@
package org.hso.texturesyncclient.controller
class NetworkController {
}

View File

@ -0,0 +1,315 @@
package org.hso.texturesyncclient.controller
import javafx.application.Platform
import javafx.collections.ObservableList
import javafx.event.EventHandler
import javafx.scene.image.Image
import javafx.stage.DirectoryChooser
import org.hso.texturesyncclient.alerts.JFXInfoAlert
import org.hso.texturesyncclient.alerts.JFXOkayCancelAlert
import org.hso.texturesyncclient.controller.net.AutoConnect
import org.hso.texturesyncclient.controller.net.Connection
import org.hso.texturesyncclient.model.GUIModel
import org.hso.texturesyncclient.model.Sha256
import org.hso.texturesyncclient.model.Texture
import org.hso.texturesyncclient.model.TextureFormat
import org.hso.texturesyncclient.view.importView.ImportView
import org.hso.texturesyncclient.view.mainView.MainView
import org.hso.texturesyncclient.view.mainView.MainViewController
import org.hso.texturesyncclient.view.startupView.StartupView
import org.hso.texturesyncclient.view.startupView.StartupViewController
import tornadofx.Controller
import tornadofx.find
import tornadofx.observable
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.InetAddress
import java.nio.file.Files
import java.text.SimpleDateFormat
import java.util.*
import javax.imageio.ImageIO
class RootController : Controller() {
private val mvc: MainViewController by inject()
private val svc: StartupViewController by inject()
private lateinit var con: Connection
private lateinit var selectedTextureModel: GUIModel
private var lastExportDir: String = System.getProperty("user.home")
/**
* calculate the resolution, get today's date -> upload to server
* @param path the absolute path of the file on the client's system
* @param name the file name
* @param tags all tags for the file
*/
fun importTexture(path: String, name: String, tags: ObservableList<String>) {
val data = Files.readAllBytes(File(path).toPath()) // this is the image as byte array
val uuid = UUID.randomUUID()
val format = if (File(path).extension.toLowerCase() == "png") TextureFormat.PNG else TextureFormat.JPEG
val bimg = ImageIO.read(File(path)) //image for obtaining resolution
val resolution = Pair(bimg.height, bimg.width)
val cal = Calendar.getInstance() //calendar obj with current time
val hash = Sha256(data)
val alertImportHash = JFXInfoAlert(
"Upload fehlgeschlagen",
"Die Textur wurde nicht fehlerfrei übertragen",
"-fx-button-type: RAISED; -fx-background-color: #2b7bbb; -fx-text-fill: #000000;"
)
val alertImport = JFXInfoAlert(
"Upload fehlgeschlagen",
"Mögliche Ursachen:" +
"\n- Textur existiert bereits" +
"\n- Server nicht bereit",
"-fx-button-type: RAISED; -fx-background-color: #2b7bbb; -fx-text-fill: #000000;"
)
val newTexture = Texture(uuid, name, tags.toTypedArray(), format, resolution, cal, hash)
try {
con.uploadTexture(newTexture, data)
println("Texture upload successful")
} catch (e: IllegalArgumentException) {
println(e)
alertImportHash.showAndWait()
} catch (e: Exception) {
alertImport.showAndWait()
println(e)
}
}
/**
* Initialize connection to server and switch to MainView if connected
* @param name server name as IP or domain
*/
fun initConnection(name: String) {
if (name == " ") {
//no user input, try automatic connect or restore address from settings file
println("try auto connect")
try {
val foundServer = AutoConnect.searchServer()
if (foundServer != null) {
println("[auto] server found")
con = foundServer
con.ping()
println("[auto] Connection to Server successful")
switchStartupToMain()
showAll()
} else {
println("[auto] no server found")
}
} catch (e: Exception) {
println(e)
println("[auto] Connection to Server NOT successful")
}
if (SettingsController.serverAddressIsSet()) {
println("[file] try connect with settings file")
try {
svc.setServerAddress(SettingsController.getServerAddress()) // FIXME this crashes the client when there is either not text or no label!
con = Connection(InetAddress.getByName(SettingsController.getServerAddress()))
con.ping()
println("[file] Connection to Server successful")
switchStartupToMain()
showAll()
} catch (e: Exception) {
println(e)
println("[file] Connection to Server NOT successful")
}
} else {
println("[file] no address in settings file")
}
} else {
//try to connect with user input
try {
println("try connect with user input")
con = Connection(InetAddress.getByName(name))
con.ping()
println("Connection to Server successful")
SettingsController.setServerAddress(name) //store address in settings file
switchStartupToMain()
showAll()
println("swithing to MainView @ initCon")
} catch (e: Exception) {
println(e)
println("Connection to Server NOT successful")
Platform.runLater {
JFXInfoAlert(
"Verbinden fehlgeschlagen",
"Mögliche Ursachen:" +
"\n- der angegebene Server ist offline" +
"\n- die benötigten Ports sind nicht offen",
"-fx-button-type: RAISED; -fx-background-color: #2b7bbb; -fx-text-fill: #000000;"
).showAndWait()
}
}
}
}
fun queryElements(tags: ObservableList<String>): ArrayList<GUIModel> {
mvc.setVisibleMetaTags(false)
val previewList = arrayListOf<GUIModel>()
try {
con.query(tags.toTypedArray()).forEach {
runAsync {
Platform.runLater {
mvc.addElement(GUIModel(it, con.getTexturePreview(it.textureHash)))
}
}
}
} catch (e: Exception) {
println(e)
}
println(previewList.size)
return previewList
}
/**
* save the texture file to a local directory
* @param data the texture as meta element
*/
fun exportTexture(data: Texture) {
val directoryChooser = DirectoryChooser()
directoryChooser.title = "Export Verzeichnis wählen"
directoryChooser.initialDirectory = File(lastExportDir)
val alertExport = JFXInfoAlert(
"Exporterien fehlgeschlagen",
"Mögliche Ursachen:" +
"\n- Datei existiert bereits" +
"\n- Ordner nicht beschreibbar",
"-fx-button-type: RAISED; -fx-background-color: #2b7bbb; -fx-text-fill: #000000;"
)
val dir = directoryChooser.showDialog(primaryStage)
if (dir != null) {
val extension = if (data.format == TextureFormat.PNG) ".png" else ".jpeg" //get file format
val filePath = "$dir/${data.name}$extension" //build absolute exported texture path
val exportedFile = File(filePath) //create file
if (exportedFile.exists()) {
alertExport.showAndWait()
} else {
try {
val fileout = FileOutputStream(exportedFile)
fileout.write(con.getTextureFile(data.textureHash)) //write bytes in file fileout.close()
fileout.close()
} catch (e: IOException) {
alertExport.showAndWait()
}
}
lastExportDir = dir.absolutePath //store last user chosen dir
}
}
/**
* show the detailed meta information in the DetailView
* @param data the texture as meta element
*/
fun showDetail(data: Texture) {
mvc.setPreview3DTexture(con.getTexturePreview(data.textureHash))
val sdf = SimpleDateFormat("dd.MM.yyyy")
mvc.setMeta(
data.name,
"${data.resolution.first}px x ${data.resolution.second}px",
data.format.toString(),
sdf.format(data.addedOn.time)
)
mvc.setTags(data.tags.toList().observable())
mvc.setVisibleMetaTags(true)
}
/**
* remove a texture from the FolderView and the server
*/
fun deleteTexture() {
val dialogDelete = JFXOkayCancelAlert(
"Löschen",
"Textur ${selectedTextureModel.data.name} wirklich löschen?",
"-fx-button-type: RAISED; -fx-background-color: #2b7bbb; -fx-text-fill: #000000;"
)
dialogDelete.okayAction = EventHandler {
con.deleteTexture(selectedTextureModel.data)
mvc.removeTextureFromView(selectedTextureModel.data)
// reset the DetailView
mvc.setVisibleMetaTags(false)
mvc.setPreview3DTexture(Image("icons/TextureSync_Icon_256x256.jpeg"))
}
dialogDelete.cancelAction = EventHandler {
// Do nothing
}
dialogDelete.showAndWait()
}
fun updateTexture(name: String, tags: Array<String>) {
val newTexture = selectedTextureModel.data.copy(
tags = tags,
name = name
)
try {
con.updateTexture(
selectedTextureModel.data,
newTexture,
con.getTextureFile(selectedTextureModel.data.textureHash)
)
selectedTextureModel.data = newTexture
} catch (e: Exception) {
println(e)
}
}
/**
* show all available textures at start
*/
private fun showAll() {
queryElements(mvc.getTags())
}
/**
* set the last selected texture-GUIModell
* @param model the last selected element
*/
fun setSelectedTexture(model: GUIModel) {
selectedTextureModel = model
selectedTexture = model.data
}
companion object {
var selectedTexture: Texture? = null
fun switchStartupToMain() {
Platform.runLater {
find(StartupView::class).replaceWith(MainView::class, sizeToScene = true, centerOnScreen = true)
}
}
// These runLater calls should be unnecessary
fun switchMainToImport() {
Platform.runLater {
find(MainView::class).replaceWith(ImportView::class, sizeToScene = true, centerOnScreen = true)
}
}
fun switchImportToMain() {
Platform.runLater {
find(ImportView::class).replaceWith(MainView::class, sizeToScene = true, centerOnScreen = true)
}
}
}
}

View File

@ -0,0 +1,79 @@
package org.hso.texturesyncclient.controller
import java.io.*
import java.util.Properties
class SettingsController {
companion object {
private lateinit var serverAddress: String
private lateinit var props: Properties
private val userHome = System.getProperty("user.home")
private val osName = System.getProperty("os.name")
private lateinit var dirPath: String //path to settings file
private lateinit var settingsFile: File //settings file
private const val defaultAddressValue: String = " "
fun init() {
props = Properties()
dirPath = if (osName.contains("Windows")) {
"$userHome/Documents/TextureSync"
} else {
"$userHome/.config/TextureSync"
}
settingsFile = File("$dirPath/config.xml") //open Settings file
if (!settingsFile.exists()) {
println("settings not found! Will create new one")
File(dirPath).mkdir()
settingsFile.createNewFile()
serverAddress = defaultAddressValue //load default value
saveSettings()
} else {
println("settings found")
loadSettings()
}
}
fun serverAddressIsSet(): Boolean {
if (serverAddress == defaultAddressValue) {
return false
}
return true
}
private fun loadSettings() {
val inputStream: InputStream
inputStream = FileInputStream(settingsFile)
props.loadFromXML(inputStream)
serverAddress = props.getProperty("serverAddress")
inputStream.close()
}
private fun saveSettings() {
val outputStream: OutputStream
props.setProperty("serverAddress", serverAddress)
outputStream = FileOutputStream(settingsFile)
props.storeToXML(outputStream, "TextureSync settings")
outputStream.close()
println("settings saved")
}
fun getServerAddress(): String {
return serverAddress
}
fun setServerAddress(serverAddress: String) {
this.serverAddress = serverAddress
saveSettings()
}
}
}

View File

@ -0,0 +1,60 @@
package org.hso.texturesyncclient.controller.net
import java.io.IOException
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
class AutoConnect private constructor() {
companion object {
@Throws(IOException::class)
fun searchServer(
mulicastAddr: String = "ff02::dd42:c0fe",
port: Int = 10796,
timeoutMs: Int = 400,
trys: Int = 10
): Connection? {
val sock = DatagramSocket()
try {
sock.soTimeout = timeoutMs
for (i in 0..trys) {
val bytes = "TextureSync".toByteArray()
sock.send(
DatagramPacket(
bytes,
0,
bytes.size,
InetAddress.getByName(mulicastAddr),
port
)
)
// Response is PortNum in BE
val portData = ByteArray(2)
val response = DatagramPacket(portData, portData.size)
try {
sock.receive(response)
// 2-Byte BE to Port Number
val serverPort = (portData[0].toInt().shl(8)).or(portData[1].toInt())
return Connection(response.address, serverPort)
} catch (e: IOException) {
// Timed out
// NOP
}
}
} catch (e: Exception) {
throw e
} finally {
sock.close()
}
return null
}
}
}

View File

@ -19,7 +19,7 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
@Throws(IOException::class)
@Synchronized
private fun getStreams(): Pair<DataInputStream, DataOutputStream> {
private fun <R> withStreams(retry: Boolean = true, inner: (DataInputStream, DataOutputStream) -> R): R {
val i: DataInputStream
val o: DataOutputStream
@ -40,31 +40,40 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
o = output!!
}
return Pair(i, o)
return try {
inner(i, o)
} catch (e: IOException) {
if (retry) {
println("Got IOException; Retry ${e.message}")
close()
withStreams(false, inner)
} else {
throw e
}
}
}
@Throws(IOException::class, ConnectionException::class)
@Synchronized
fun ping() {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("ping", JsonObject())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
is JsonPackage -> return
when (val pkg = Package.read(i)) {
is JsonPackage -> return@withStreams
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
is ErrorPackage -> throw ConnectionErrorException(pkg)
else -> throw RuntimeException("Unreachable")
}
};
}
@Throws(IOException::class, ConnectionException::class)
@Synchronized
fun query(query: Array<String>): Array<Texture> {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("query", {
@ -79,12 +88,15 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
inner
}())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
when (val pkg = Package.read(i)) {
is JsonPackage -> {
try {
return Gson().fromJson<Array<InternalTexture>>(pkg.content, Array<InternalTexture>::class.java)
return@withStreams Gson().fromJson<Array<InternalTexture>>(
pkg.content,
Array<InternalTexture>::class.java
)
.map { tex ->
tex.toTexture()
}.toTypedArray()
@ -97,11 +109,12 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
else -> throw RuntimeException("Unreachable")
}
}
}
@Throws(IOException::class, ConnectionException::class)
@Synchronized
fun getTextureById(id: UUID): Texture? {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("get_texture", {
@ -110,11 +123,11 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
inner
}())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
when (val pkg = Package.read(i)) {
is JsonPackage -> {
return if (pkg.content.isJsonNull) {
return@withStreams if (pkg.content.isJsonNull) {
null
} else {
try {
@ -132,11 +145,11 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
}
}
}
@Throws(IOException::class, ConnectionException::class)
@Synchronized
fun getTextureByName(name: String): Texture? {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("get_texture", {
@ -145,11 +158,11 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
inner
}())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
when (val pkg = Package.read(i)) {
is JsonPackage -> {
return if (pkg.content.isJsonNull) {
return@withStreams if (pkg.content.isJsonNull) {
null
} else {
try {
@ -165,13 +178,12 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
is ErrorPackage -> throw ConnectionErrorException(pkg)
else -> throw RuntimeException("Unreachable")
}
}
}
@Throws(IOException::class, ConnectionException::class)
@Synchronized
fun getTextureFile(hash: Sha256): ByteArray {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("get_texture_file", {
@ -180,22 +192,20 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
inner
}())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
when (val pkg = Package.read(i)) {
is JsonPackage -> throw ConnectionUnexpectedPacketException()
is BinaryPackage -> return pkg.content
is BinaryPackage -> return@withStreams pkg.content
is ErrorPackage -> throw ConnectionErrorException(pkg)
else -> throw RuntimeException("Unreachable")
}
}
}
@Throws(IOException::class, ConnectionException::class)
@Synchronized
fun getTexturePreview(hash: Sha256): Image {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("get_texture_preview", {
val inner = JsonObject()
@ -204,22 +214,23 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
inner
}())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
when (val pkg = Package.read(i)) {
is JsonPackage -> throw ConnectionUnexpectedPacketException()
is BinaryPackage -> {
return Image(ByteArrayInputStream(pkg.content))
return@withStreams Image(ByteArrayInputStream(pkg.content))
}
is ErrorPackage -> throw ConnectionErrorException(pkg)
else -> throw RuntimeException("Unreachable")
}
}
}
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
@Synchronized
private fun replaceTexture(old: Texture?, new: Texture?, image: ByteArray?) {
val io = getStreams()
return withStreams { i, o ->
val obj = JsonObject()
obj.add("replace_texture", {
@ -237,19 +248,19 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
inner
}())
JsonPackage(obj).write(io.second)
JsonPackage(obj).write(o)
when (val pkg = Package.read(io.first)) {
when (val pkg = Package.read(i)) {
is JsonPackage -> {
if (pkg.content == JsonPrimitive(true)) {
// everthing is fine!
return
} else if (image != null) {
when {
pkg.content == JsonPrimitive(true) -> // everthing is fine!
return@withStreams
image != null -> {
// should be { "get_texture_file": { texture_hash : <Hash> }}
// we don't check, since there is no good way to handle it.
BinaryPackage(image).write(io.second)
when (val ipkg = Package.read(io.first)) {
BinaryPackage(image).write(o)
when (val ipkg = Package.read(i)) {
is JsonPackage -> {
if (ipkg.content != JsonPrimitive(true)) {
// Protokoll Assertion failed
@ -260,20 +271,22 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
is ErrorPackage -> throw ConnectionErrorException(ipkg)
else -> throw RuntimeException("Unreachable")
}
} else {
ErrorPackage(404, "Texture not found!").write(io.second)
}
else -> {
ErrorPackage(404, "Texture not found!").write(o)
close() // gets re-opened on next request.
throw IllegalArgumentException("Image Argument was needed.")
}
}
}
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
is ErrorPackage -> throw ConnectionErrorException(pkg)
else -> throw RuntimeException("Unreachable")
}
}
}
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
@Synchronized
fun uploadTexture(texture: Texture, image: ByteArray) {
if (texture.textureHash != Sha256(image)) {
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
@ -283,7 +296,6 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
}
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
@Synchronized
fun updateTexture(old: Texture, new: Texture, image: ByteArray) {
if (new.textureHash != Sha256(image)) {
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
@ -293,14 +305,13 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
}
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
@Synchronized
fun deleteTexture(texture: Texture) {
replaceTexture(texture, null, null)
}
@Throws(IOException::class)
@Synchronized
override fun close() {
try {
if (output != null) {
output!!.close()
output = null
@ -313,6 +324,9 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
socket!!.close()
socket = null
}
} catch (e: IOException) {
// Ignore
}
}
}

View File

@ -30,7 +30,7 @@ internal data class InternalTexture(
resolution = arrayOf(tex.resolution.first, tex.resolution.second),
added_on = arrayOf(
tex.addedOn.get(Calendar.YEAR), //
tex.addedOn.get(Calendar.MONTH), //
tex.addedOn.get(Calendar.MONTH) + 1, //
tex.addedOn.get(Calendar.DAY_OF_MONTH)
),
texture_hash = tex.textureHash.toString()
@ -50,7 +50,7 @@ internal data class InternalTexture(
else -> throw ConnectionInvalidJsonException()
},
resolution = Pair(resolution[0], resolution[1]),
addedOn = GregorianCalendar(added_on[0], added_on[1], added_on[2]),
addedOn = GregorianCalendar(added_on[0], added_on[1] - 1, added_on[2]),
textureHash = Sha256(texture_hash)
)
} catch (e: Exception) { // i Know, but no time :[]

View File

@ -6,7 +6,7 @@ import java.lang.Exception
sealed class ConnectionException(override val message: String) : Exception(message)
class ConnectionErrorException(val errorCode: Int, val errorMessage: String) :
class ConnectionErrorException(errorCode: Int, errorMessage: String) :
ConnectionException("$errorCode $errorMessage") {
internal constructor(err: ErrorPackage) : this(err.code, err.message)
}

View File

@ -13,7 +13,7 @@ enum class TextureFormat {
data class Texture(
val id : UUID,
val name : String,
val tags : Array<String>,
var tags : Array<String>,
val format : TextureFormat,
val resolution : Pair<Int, Int>,
val addedOn : Calendar,

View File

@ -12,30 +12,65 @@ import javafx.scene.layout.BackgroundFill
import javafx.scene.layout.CornerRadii
import javafx.scene.layout.VBox
import javafx.scene.paint.Paint
import tornadofx.*
import tornadofx.addClass
import tornadofx.find
import tornadofx.paddingTop
class GUIModel constructor(data: Texture, img: Image) : VBox(){
class GUIModel constructor(var data: Texture, img: Image) : VBox() {
private var image = ImageView()
private var label = Label()
private var contextMenu = ContextMenu()
var exportItem = MenuItem("exportiern")
private var exportItem = MenuItem("exportiern")
private var deleteItem = MenuItem("löschen")
private val gmc = find(GUIModelController::class)
init {
super.setPadding(Insets(5.0, 5.0, 5.0, 5.0))
super.getChildren().addAll(image, label)
super.setOnContextMenuRequested { p0 -> contextMenu.show(this@GUIModel, p0.screenX, p0.screenY) }
super.setOnMouseClicked {
if (gmc.isLastSelectedInitialized()) {
gmc.lastSelected.background = Background.EMPTY
this.background = Background(BackgroundFill(Paint.valueOf("#2b7bbb"), CornerRadii.EMPTY, Insets.EMPTY))
gmc.lastSelected = this
} else {
this.background = Background(BackgroundFill(Paint.valueOf("#2b7bbb"), CornerRadii.EMPTY, Insets.EMPTY))
gmc.lastSelected = this
}
gmc.previewSelectedAction(data)
gmc.setSelected(this)
}
label.addClass("metadata")
label.paddingTop = 5.0
label.prefWidth = 128.0
label.alignment = Pos.CENTER
label.text = data.name
label.background = Background(BackgroundFill(Paint.valueOf("#FFFF2b"), CornerRadii.EMPTY, Insets.EMPTY))
label.text = if (data.name.length > 15) {
"${data.name.subSequence(0, 14)}.."
} else {
data.name
}
label.background = Background(BackgroundFill(Paint.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
image.fitHeight = 128.0
image.fitWidth = 128.0
image.image = img
exportItem.setOnAction {
gmc.export(data)
}
deleteItem.setOnAction {
gmc.delete()
}
contextMenu.items.add(exportItem)
contextMenu.items.add(deleteItem)
}
}

View File

@ -0,0 +1,28 @@
package org.hso.texturesyncclient.model
import org.hso.texturesyncclient.controller.RootController
import tornadofx.Controller
class GUIModelController : Controller() {
private val rootc = find(RootController::class)
lateinit var lastSelected: GUIModel
fun isLastSelectedInitialized() = ::lastSelected.isInitialized
fun export(data: Texture) {
rootc.exportTexture(data)
}
fun delete() {
rootc.deleteTexture()
}
fun previewSelectedAction(data: Texture) {
rootc.showDetail(data)
}
fun setSelected(model: GUIModel) {
rootc.setSelectedTexture(model)
}
}

View File

@ -3,7 +3,6 @@ package org.hso.texturesyncclient.view.importView
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXChipView
import com.jfoenix.controls.JFXTextField
import javafx.collections.ListChangeListener
import javafx.geometry.Pos
import javafx.scene.layout.Background
import javafx.geometry.Insets
@ -11,24 +10,30 @@ import javafx.scene.layout.BackgroundFill
import javafx.scene.layout.CornerRadii
import javafx.scene.paint.Paint
import javafx.scene.layout.Priority
import org.hso.texturesyncclient.view.mainView.Preview3D
import tornadofx.*
class ImportView : View() {
class ImportView : View("TextureSync") {
val tfFilePath = JFXTextField()
val tfName = JFXTextField()
val cvTags = JFXChipView<String>()
val btnImport = JFXButton("Importieren")
val preview = Preview3D()
private val btnBack = JFXButton("Zurück")
private val ivc: ImportViewController by inject()
init {
btnImport.isVisible = false
preview.root.isVisible = false
}
override val root = borderpane {
minWidth = 1000.0
minHeight = 500.0
prefWidth = FX.primaryStage.width
prefHeight = FX.primaryStage.height
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
center = vbox(50) {
@ -39,6 +44,8 @@ class ImportView : View() {
style = "-fx-font: 20px Verdana; -fx-text-fill: #2b7bbb;"
}
add(preview)
vbox(20) {
hbox(10) {
add(tfFilePath)
@ -69,9 +76,8 @@ class ImportView : View() {
vbox(5) {
alignment = Pos.CENTER
add(btnImport)
add(btnBack)
}
}
}
@ -83,16 +89,17 @@ class ImportView : View() {
tfName.style = "-fx-text-fill: #b15b2e;"
tfName.promptText = "Name eingeben"
cvTags.style = "-fx-background-color: #3c3f41; -fx-text-inner-color: #b15b2e;"
cvTags.style = "-fx-background-color: #53585b; -fx-text-inner-color: #b15b2e;"
//TODO change color of Chip´s see: https://github.com/jfoenixadmin/JFoenix/blob/master/jfoenix/src/main/resources/com/jfoenix/assets/css/controls/jfx-chip-view.css#L52
btnImport.style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb;"
btnImport.style = "-fx-button-type: RAISED; -fx-background-color: #b15b2e; -fx-text-fill: #3c3f41;"
btnBack.style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb; -fx-padding: 10;"
}
tfFilePath.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
tfName.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
cvTags.chips.addListener { change: ListChangeListener.Change<out String>? ->
cvTags.chips.onChange {
ivc.validateImport()
}
@ -100,6 +107,10 @@ class ImportView : View() {
ivc.btnImportAction()
}
btnBack.setOnAction {
ivc.btnBackAction()
}
}
}

View File

@ -1,32 +1,68 @@
package org.hso.texturesyncclient.view.importView
import javafx.stage.FileChooser.ExtensionFilter
import javafx.scene.image.Image
import javafx.stage.FileChooser
import org.hso.texturesyncclient.controller.RootController
import tornadofx.Controller
import tornadofx.FileChooserMode
import tornadofx.chooseFile
import java.io.File
import java.io.FileInputStream
class ImportViewController : Controller() {
private val iv = find(ImportView::class)
private val rootc = find(RootController::class)
fun btnFileChooserAction() {
val list = listOf("*.png", "*.PNG", "*.jpg", "*.JPG", "*.jpeg", "*.JPEG")
val arrayFilter = arrayOf(ExtensionFilter("Texturen vom Bildformat: PNG oder JPG", list))
val file = chooseFile("Textur auswählen", arrayFilter, FileChooserMode.Single, owner = null)
private var lastImportDir: String = System.getProperty("user.home")
if (file.isNotEmpty()) {
iv.tfFilePath.text = file[0].absolutePath
iv.tfName.text = file[0].nameWithoutExtension
fun btnFileChooserAction() {
val fileChooser = FileChooser()
val fileExtensions = FileChooser.ExtensionFilter(
"Texturen vom Bildformat: PNG oder JPEG", "*.png", "*.PNG", "*.jpg", "*.JPG", "*.jpeg", "*.JPEG"
)
fileChooser.extensionFilters.addAll(fileExtensions)
fileChooser.initialDirectory = File(lastImportDir)
fileChooser.title = "Textur auswählen"
val file = fileChooser.showOpenDialog(null)
if (file != null) {
iv.tfFilePath.text = file.absolutePath
iv.tfName.text = file.nameWithoutExtension
lastImportDir = file.parent.toString() //store last user chosen dir
runAsync {
try {
val fileInput = FileInputStream(file.absolutePath)
val img = Image(fileInput)
iv.preview.setTexture(img)
iv.preview.root.isVisible = true
} catch (e: Exception) {
// Got a catch'em all
println(e.message)
}
}
}
}
fun btnImportAction() {
rootc.importTexture(iv.tfFilePath.text, iv.tfName.text, iv.cvTags.chips)
RootController.switchImportToMain()
iv.tfFilePath.clear()
iv.tfName.clear()
iv.cvTags.chips.clear()
}
fun validateImport() {
iv.btnImport.isVisible = iv.tfFilePath.text.isNotEmpty() && iv.tfName.text.isNotEmpty() && iv.cvTags.chips.isNotEmpty() && iv.cvTags.chips.stream().allMatch { x -> x.length < 32 }
iv.btnImport.isVisible =
iv.tfFilePath.text.isNotEmpty() && iv.tfName.text.isNotEmpty() && iv.cvTags.chips.isNotEmpty() && iv.cvTags.chips.stream().allMatch { x -> x.length < 32 }
}
fun btnBackAction() {
RootController.switchImportToMain()
iv.tfFilePath.clear()
iv.tfName.clear()
iv.cvTags.chips.clear()
}
}

View File

@ -1,9 +1,10 @@
package org.hso.texturesyncclient.view.mainView
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXChipView
import com.jfoenix.controls.JFXTextField
import javafx.geometry.Insets
import javafx.geometry.Orientation
import javafx.scene.control.Label
import javafx.scene.image.Image
import javafx.scene.layout.Background
import javafx.scene.layout.BackgroundFill
@ -14,40 +15,66 @@ import tornadofx.*
class DetailView : View() {
val preview = Preview3D()
val metaLabel = Label("Auflösung: 8MP\nName: Texture.png\nAndere: was anderes")
val cvTags = JFXChipView<String>()
init {
// set a default texture
preview.setTexture(Image("textures/sample_texture_1.jpg"))
val nameInfo = JFXTextField().addClass("metadata")
val resolutionInfo = label().addClass("metadata")
val formatInfo = label().addClass("metadata")
val dateInfo = label().addClass("metadata")
val btnSubmit = JFXButton("Ändern").addClass("btn-blue")
val metadataPanel = gridpane {
row {
label("Name ").addClass("metadata")
add(nameInfo)
}
row {
label("Auflösung ").addClass("metadata")
add(resolutionInfo)
}
row {
label("Format ").addClass("metadata")
add(formatInfo)
}
row {
label("Einfügedatum ").addClass("metadata")
add(dateInfo)
}
}
override val root = form {
minWidth = 250.0
background = Background(BackgroundFill(Paint.valueOf("#9f9f9f"), CornerRadii.EMPTY, Insets.EMPTY))
background = Background(BackgroundFill(Paint.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
fieldset("DetailView", labelPosition = Orientation.VERTICAL) {
fieldset(labelPosition = Orientation.VERTICAL) {
field("3D Preview") {
field {
vbox(7) {
add(preview)
}
}
field("Meta") {
add(metaLabel)
field {
add(metadataPanel)
}
field("Tags") {
field {
minHeight = 155.0
add(cvTags)
}
// TODO add "Import" Btn
}
style {
cvTags.style = "-fx-background-color: #3c3f41; -fx-text-inner-color: #b15b2e;"
field {
add(btnSubmit)
}
}
}
init {
// set a default texture
preview.setTexture(Image("icons/TextureSync_Icon_256x256.jpeg"))
btnSubmit.useMaxWidth = true
}
}

View File

@ -10,14 +10,10 @@ import tornadofx.*
class FolderView : View("FolderView"){
override val root = flowpane {
hgap = 10.0
vgap = 10.0
paddingAll = 5.0
prefWidth = 750.0
background = Background(BackgroundFill(Paint.valueOf("#cfcfcf"), CornerRadii.EMPTY, Insets.EMPTY))
style {
hgap = 5.0
vgap = 5.0
paddingAll = 10.0
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
}
}
}
}

View File

@ -1,41 +1,99 @@
package org.hso.texturesyncclient.view.mainView
import javafx.collections.ListChangeListener
import javafx.scene.image.Image
import com.jfoenix.controls.JFXButton
import com.jfoenix.controls.JFXChipView
import javafx.geometry.Insets
import javafx.scene.layout.Background
import javafx.scene.layout.BackgroundFill
import javafx.scene.layout.CornerRadii
import javafx.scene.paint.Paint
import tornadofx.*
class MainView : View() {
class MainView : View("TextureSync") {
val cvSearch = JFXChipView<String>()
private val btnImport = JFXButton("+")
val folderView = find(FolderView::class)
val detailView = find(DetailView::class)
private val mvc: MainViewController by inject()
override val root = borderpane {
minWidth = 1000.0
minHeight = 500.0
override val root = anchorpane {
left = folderView.root
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
prefWidth = FX.primaryStage.width
prefHeight = FX.primaryStage.height
borderpane {
right = detailView.root
center = vbox {
add(cvSearch)
scrollpane {
style = "-fx-background-color:transparent;"
isFitToWidth = true
isFitToHeight = true
add(folderView.root)
}
}
anchorpaneConstraints {
topAnchor = 0
bottomAnchor = 0
rightAnchor = 0
leftAnchor = 0
}
}
add(btnImport)
style {
// style options
cvSearch.promptText = "Suche"
cvSearch.paddingAll = 5.0
cvSearch.minHeight = 65.0
cvSearch.style = "-fx-background-color: #53585b; -fx-text-inner-color: #b15b2e;"
btnImport.buttonType = JFXButton.ButtonType.RAISED
btnImport.styleClass.add("jfx-floating-action-button")
btnImport.anchorpaneConstraints {
bottomAnchor = 5
rightAnchor = 5
}
}
// actions
// folderView.btn1.setOnAction {
// mvc.setPreview3DTexture(Image("textures/sample_texture_1.jpg"))
// mvc.setMeta("texture 1", "8MP", "Quelle: wikipedia")
// mvc.setTags(observableList("Stein", "Rot", "super"))
// }
cvSearch.chips.onChange {
mvc.cvSearchAction(cvSearch.chips)
}
detailView.cvTags.chips.addListener { change: ListChangeListener.Change<out String>? ->
btnImport.setOnAction {
mvc.btnImportAction()
}
// TODO: on chipview update on name update
detailView.cvTags.chips.onChange {
detailView.btnSubmit.isVisible = true
}
detailView.nameInfo.textProperty().onChange {
detailView.btnSubmit.isVisible = true
}
detailView.btnSubmit.setOnAction {
mvc.updateTags()
detailView.btnSubmit.isVisible = false
}
//keyboard actions
shortcut("Ctrl+I") {
mvc.btnImportAction()
}
shortcut("Ctrl+E") {
mvc.scExport()
}
}
}
}

View File

@ -2,82 +2,105 @@ package org.hso.texturesyncclient.view.mainView
import javafx.collections.ObservableList
import javafx.scene.image.Image
import org.hso.texturesyncclient.controller.RootController
import org.hso.texturesyncclient.model.GUIModel
import org.hso.texturesyncclient.model.TextureFormat
import org.hso.texturesyncclient.model.Texture
import tornadofx.Controller
import javafx.stage.DirectoryChooser
import javax.swing.JColorChooser.showDialog
import java.io.File
class MainViewController : Controller() {
private val mv = find(MainView::class)
private val rootc = find(RootController::class)
// FolderView elements
private val folderView = mv.folderView.root
// DetailView elements
private val preview = mv.detailView.preview
private val metaLabel = mv.detailView.metaLabel
private val cvTags = mv.detailView.cvTags
private var lockUpdate: Boolean = false //lock update func when the system changes the detailview chipview
// FolderView functions
fun addElement(element: GUIModel) {
folderView.children.add(element)
}
fun addAllElements(elementList: List<GUIModel>) {
folderView.children.addAll(elementList)
}
// DetailView functions
fun setPreview3DTexture(img: Image) {
preview.setTexture(img)
}
fun setMeta(name: String, res: String, etc: String) {
metaLabel.text = "Name: $name\nAuflösung: $res\nAnderes: $etc"
fun setMeta(name: String, res: String, format: String, date: String) {
with(mv.detailView) {
nameInfo.text = name
formatInfo.text = format
resolutionInfo.text = res
dateInfo.text = date
}
}
fun setTags(chips: ObservableList<String>) {
lockUpdate = false //dont trigger update with onChange
cvTags.chips.clear()
cvTags.chips.addAll(chips)
lockUpdate = true //allow update with onChange
}
fun getTags(): ObservableList<String> {
return cvTags.chips
}
// update the tags for the selected element
fun updateTags() {
println(cvTags.chips)
if (lockUpdate) { //the chipView was changed by the user
println("Tags changed")
rootc.updateTexture(
tags = cvTags.chips.toTypedArray(),
name = mv.detailView.nameInfo.text
)
}
}
// DetailView actions
/**
* save the texture file to r
* @param data the file as a byte array
* @param name name for the file
* @param format specific file format. jpeg or png
*/
fun exportTexture(data: ByteArray, name: String, format : TextureFormat){
val directoryChooser = DirectoryChooser()
directoryChooser.title = "Export Verzeichnis wählen"
// TODO directoryChooser.setInitialDirectory(new File(System.getProperty("user.home")))
val dir = directoryChooser.showDialog(primaryStage)
if (dir != null) {
//copy data (bytesarray) with name and extension to dir
fun cvSearchAction(tags: ObservableList<String>) {
// show spinner, block ui
folderView.children.clear()
mv.cvSearch.isDisable = true
setPreview3DTexture(Image("icons/TextureSync_Icon_256x256.jpeg")) // reset the 3DPreview to the logo
RootController.selectedTexture = null
runAsync {
rootc.queryElements(tags)
} ui {
// when search finished
mv.cvSearch.isDisable = false
}
}
fun btnImportAction() {
RootController.switchMainToImport()
}
fun removeTextureFromView(data: Texture) {
// stream all children nodes, filter them as GUIModel with data.id == data.id, for any found object if it's still present remove it from the folderView
folderView.children.stream().filter { x -> (x as GUIModel).data.id == data.id }.findAny()
.ifPresent { x -> folderView.children.remove(x) }
}
fun setVisibleMetaTags(bool: Boolean) {
if (bool) {
mv.detailView.metadataPanel.isVisible = true
mv.detailView.btnSubmit.isVisible = false
cvTags.isVisible = true
} else {
mv.detailView.metadataPanel.isVisible = false
mv.detailView.btnSubmit.isVisible = false
cvTags.isVisible = false
}
}
fun scExport() {
RootController.selectedTexture?.let { rootc.exportTexture(RootController.selectedTexture!!) }
}
}

View File

@ -36,7 +36,6 @@ class Preview3D : View("Preview3D") {
pointLightFront.rotate = 90.0
}
override val root = stackpane {
add(dBox).apply {
@ -56,7 +55,7 @@ class Preview3D : View("Preview3D") {
style {
minWidth = 200.px
minHeight = 200.px
background = Background(BackgroundFill(Color.BLUEVIOLET, CornerRadii.EMPTY, Insets.EMPTY))
background = Background(BackgroundFill(Color.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
}
}

View File

@ -13,7 +13,7 @@ import javafx.scene.layout.CornerRadii
import javafx.scene.paint.Paint
import tornadofx.*
class StartupView : View("StartupView") {
class StartupView : View("TextureSync") {
val labelStatus = Label("Verbindung zum Server einrichten")
val spinnerStatus = JFXSpinner()
@ -22,6 +22,7 @@ class StartupView : View("StartupView") {
private val svc: StartupViewController by inject()
override val root = borderpane {
minWidth = 1000.0
minHeight = 500.0

View File

@ -1,6 +1,5 @@
package org.hso.texturesyncclient.view.startupView
import kotlinx.coroutines.withTimeout
import org.hso.texturesyncclient.controller.RootController
import tornadofx.Controller
@ -10,23 +9,53 @@ class StartupViewController : Controller() {
private val sv = find(StartupView::class)
private val rootc = find(RootController::class)
fun initConnection() {
println("init StartupViewController")
startConnectionUI()
runAsync {
rootc.initConnection(" ")
} ui {
// reset for later use
endConnectionUI()
}
}
fun setServerAddress(address: String) {
//sv.tfServerIP.text = address
sv.tfServerIP.isFocusTraversable = false
}
fun btnConnectAction(name: String) {
startConnectionUI()
runAsync {
rootc.initConnection(name)
} ui {
// reset for later use
endConnectionUI()
}
}
/**
* show spinner and block textfied + button and set label
*/
private fun startConnectionUI() {
sv.labelStatus.text = "Verbinden ..."
sv.tfServerIP.isEditable = false
sv.btnConnect.isDisable = true
sv.spinnerStatus.isVisible = true
}
runAsync() {
rootc.initConnection(name)
} ui {
// reset for later use
/**
* remove spinner and unblock textfied + button and set label
*/
private fun endConnectionUI() {
sv.spinnerStatus.isVisible = false
sv.labelStatus.text = "Verbindung zum Server einrichten"
sv.tfServerIP.isEditable = true
sv.btnConnect.isDisable = false
sv.tfServerIP.clear()
}
}
}

View File

@ -0,0 +1,145 @@
/*******************************************************************************
* *
* Scroll Bar *
* *
******************************************************************************/
.scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background {
-fx-background-color: #2b2b2b;
-fx-background-insets: 0.0;
}
.scroll-bar:vertical > .thumb, .scroll-bar:horizontal > .thumb {
-fx-background-color: #53585b;
-fx-background-insets: 0.0;
-fx-background-radius: 1.0;
}
/* Up- and Down-Button Padding */
.scroll-bar:vertical > .increment-button, .scroll-bar:vertical > .decrement-button {
-fx-padding: 5 2 5 2;
}
/* Left- and Right-Button Padding */
.scroll-bar:horizontal > .increment-button, .scroll-bar:horizontal > .decrement-button {
-fx-padding: 2 5 2 5;
}
.scroll-bar > .increment-button, .scroll-bar > .decrement-button, .scroll-bar:hover > .increment-button, .scroll-bar:hover > .decrement-button {
-fx-background-color: transparent;
}
.scroll-bar > .increment-button > .increment-arrow, .scroll-bar > .decrement-button > .decrement-arrow {
-fx-background-color: rgb(150.0, 150.0, 150.0);
}
/* Up Arrow */
.scroll-bar:vertical > .increment-button > .increment-arrow {
-fx-shape: "M298 426h428l-214 214z";
}
/* Down Arrow */
.scroll-bar:vertical > .decrement-button > .decrement-arrow {
-fx-shape: "M298 598l214-214 214 214h-428z";
}
/* Right Arrow */
.scroll-bar:horizontal > .increment-button > .increment-arrow {
-fx-shape: "M0 428l0 -428l214 214l-214 214z";
}
/* Left Arrow */
.scroll-bar:horizontal > .decrement-button > .decrement-arrow {
-fx-shape: "M214 0l0 428l-214 -214l214 -214z";
}
/*******************************************************************************
* *
* Scroll Pane *
* *
******************************************************************************/
.scroll-pane {
-fx-background-insets: 0;
-fx-padding: 0;
}
.scroll-pane:focused {
-fx-background-insets: 0;
}
.scroll-pane .corner {
-fx-background-insets: 0;
}
/*******************************************************************************
* *
* Import Button *
* *
******************************************************************************/
.jfx-floating-action-button {
-fx-background-color: #F1F1F1;
-fx-background-radius: 50px;
-fx-pref-height: 50px;
-fx-pref-width: 50px;
-fx-min-width: -fx-pref-width;
-fx-max-width: -fx-pref-width;
-fx-min-height: -fx-pref-height;
-fx-max-height: -fx-pref-height;
-jfx-button-type: RAISED;
-fx-text-inner-color: #2b2b2b;
-fx-font-size: 20px;
}
/*******************************************************************************
* *
* ChipView *
* *
******************************************************************************/
.jfx-chip-view .jfx-chip > HBox {
-fx-font-family: "Roboto Medium";
-fx-font-size: 14.0;
-fx-background-color: #E0E0E0;
-fx-background-radius: 40px;
-fx-padding: 5px 8px 5px 12px;
-fx-pref-height: 32px;
-fx-alignment: center-left;
-fx-spacing: 8;
}
.jfx-chip-view {
-fx-text-inner-color: #E0E0E0;
}
/*******************************************************************************
* *
* Buttons *
* *
******************************************************************************/
.btn-blue {
-fx-button-type: RAISED;
-fx-background-color: #3c3f41;
-fx-text-fill: #2b7bbb;
-fx-padding: 10;
}
.btn-orange {
-fx-button-type: RAISED;
-fx-background-color: #b15b2e;
-fx-text-fill: #3c3f41;
}
/*******************************************************************************
* *
* DetailView *
* *
******************************************************************************/
.metadata {
-fx-font: 14px Verdana;
-fx-text-fill: #E0E0E0;
}

View File

@ -0,0 +1 @@
<svg width="640" height="640" xmlns="http://www.w3.org/2000/svg"><defs><radialGradient id="a" cx="0.2" cy="-0.5" r="1.7"><stop offset="40%" stop-color="#8c8c8c"/><stop offset="100%" stop-color="#4c4c4c"/></radialGradient><radialGradient id="b" cx="0.2" cy="0.2" fx="0.34" fy="0.32" r="0.9"><stop offset="0%" stop-color="#fbf6f0"/><stop offset="50%" stop-color="#eadec4"/><stop offset="100%" stop-color="#aba390"/></radialGradient><radialGradient id="c" cx="0.2" cy="0.2" fx="0.34" fy="0.32" r="0.9"><stop offset="5%" stop-color="#fbf1cd"/><stop offset="50%" stop-color="#ffba00"/><stop offset="100%" stop-color="#bc8602"/></radialGradient></defs><path d="M158.58 102.89C144.48 110.79 143.14 130.93 155.49 141.35C169.62 153.27 184.17 164.75 199.13 175.69C138.27 240.95 105.0 337.14 105.0 426.0C105.0 525.49 179.5 590.92 278.99 591.0C342.5 591.05 406.69 553.62 449.59 506.78C501.0 450.65 531.88 370.14 532.0 294.02C532.06 254.61 524.06 213.61 505.46 178.86C487.92 146.11 450.51 118.09 413.38 117.01C388.12 116.27 362.24 125.41 340.98 139.06C331.11 145.4 321.78 152.65 313.04 160.6C313.88 131.47 312.82 102.25 309.97 73.31C308.35 56.96 293.5 46.31 277.86 51.34C236.62 64.58 196.37 81.72 158.58 102.89Z" fill="#000"/><path d="M165.42 115.11C158.26 119.12 158.24 125.36 164.51 130.65C201.33 161.71 241.11 189.73 283.36 212.86C290.09 216.54 295.26 213.92 296.04 206.29C300.48 162.65 300.34 118.34 296.03 74.69C295.17 65.95 290.5 61.98 282.14 64.66C241.79 77.63 202.4 94.39 165.42 115.11Z" fill="url(#a)"/><path d="M220.6 190.82C238.75 203.14 257.44 214.63 276.64 225.14C282.11 228.13 288.25 228.46 293.76 226.78L496.27 366.27C484.19 411.93 461.33 455.47 430.41 489.22C397.54 525.11 350.51 555.58 301.99 563.18L136.05 367.03C147.1 301.77 176.06 237.27 220.6 190.82Z" fill="url(#b)"/><path d="M293.76 226.78C302.1 224.23 309.01 217.1 309.96 207.71C310.21 205.29 310.44 202.86 310.66 200.43C323.57 185.24 338.43 171.6 355.02 160.94C371.91 150.09 392.56 142.41 412.62 142.99C440.73 143.82 469.27 166.35 482.54 191.14C499.13 222.12 506.06 258.84 506.0 293.98C505.96 317.93 502.59 342.39 496.27 366.27L330.0 369.0L301.99 563.18C294.35 564.38 286.68 565.01 279.01 565.0C192.51 564.93 131.0 512.5 131.0 426.0C131.0 406.59 132.7 386.78 136.05 367.03L290.0 332.0L293.76 226.78Z" fill="url(#c)"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
- herunterladen (Release Gitea)
- installiern (Java VM)
- starten
- connecten
- neue textur einfügen
- query mit Tags
- query mit Name
- query mit Auflösung
- query mit Datum
- query mit Kombination
- exportieren
- ändern
- löschen
- settings file löschen

View File

@ -0,0 +1,7 @@
- herunterladen (Release Gitea)
- installiern (service daemon)
- starten
- loggen
- stoppen
- backups machen
- backups wieder einspielen

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,4 @@
- kleine Präsentation über Projekt
- Idee (Problem der Verwaltung vieler Texturen)
- Ziele (einfach, lokale Daten, gemeinschafftlicher Zugriff)
- Gelerntes (gute Planung, Tests, Versionsverwaltung, Issue-Tracker)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,9 @@
<phases/>
<calendars>
<day-types>
<day-type id="0" name="Working" description="A default working day"/>
<day-type id="1" name="Nonworking" description="A default non working day"/>
<day-type id="2" name="Use base" description="Use day from base calendar"/>
<day-type id="0" name="Arbeiten" description="Ein Vorgabe-Arbeitstag"/>
<day-type id="1" name="Nicht Arbeiten" description="Ein Vorgabetag, an dem nicht gearbeitet wird"/>
<day-type id="2" name="Basis verwenden" description="Tag vom Basiskalender verwenden"/>
</day-types>
<calendar id="1" name="Vorgabe">
<default-week mon="1" tue="0" wed="0" thu="1" fri="1" sat="0" sun="1"/>
@ -16,7 +16,9 @@
<interval start="1300" end="1700"/>
</overridden-day-type>
</overridden-day-types>
<days/>
<days>
<day date="20190613" type="day-type" id="0"/>
</days>
</calendar>
</calendars>
<tasks>
@ -92,88 +94,82 @@
<predecessor id="1" predecessor-id="10" type="FS"/>
</predecessors>
</task>
<task id="19" name="Realisation" note="" work="1065600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="19" name="Realisation" note="" work="1641600" start="20190416T133640Z" end="20190612T170000Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="18" type="FS"/>
</predecessors>
<task id="20" name="Einrichtung von Tests" note="" work="28800" start="20190423T000000Z" end="20190423T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="20" name="Einrichtung von Tests" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="11" type="FS"/>
</predecessors>
<task id="21" name="Client Tests" note="" work="28800" start="20190423T000000Z" end="20190423T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
</task>
</task>
<task id="22" name="Client" note="" work="547200" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="21" name="Client" note="" work="1123200" start="20190416T133640Z" end="20190612T170000Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="11" type="FS"/>
</predecessors>
<task id="23" name="UI" note="" work="288000" start="20190423T170000Z" end="20190430T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="21" type="FS"/>
</predecessors>
<task id="24" name="Previews" note="" work="86400" start="20190423T170000Z" end="20190430T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
<task id="25" name="Filter UI" note="" work="86400" start="20190423T170000Z" end="20190430T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
<task id="26" name="3D Visualisierung" note="" work="57600" start="20190423T170000Z" end="20190427T170000Z" work-start="20190424T080000Z" percent-complete="50" priority="0" type="normal" scheduling="fixed-work"/>
<task id="27" name="Dateien hinzuf&#xFC;gen" note="" work="28800" start="20190423T170000Z" end="20190424T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
<task id="28" name="Dateien lokal abspeichern" note="" work="28800" start="20190423T170000Z" end="20190424T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
<task id="22" name="UI" note="" work="288000" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="23" name="Previews" note="" work="86400" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
<task id="24" name="Filter UI" note="" work="86400" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
<task id="25" name="3D Visualisierung" note="" work="57600" start="20190416T133640Z" end="20190420T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
<task id="26" name="Dateien hinzuf&#xFC;gen" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
<task id="27" name="Dateien lokal abspeichern" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
</task>
<task id="29" name="Grund Architektur" note="" work="86400" start="20190423T000000Z" end="20190427T170000Z" work-start="20190423T080000Z" percent-complete="70" priority="0" type="normal" scheduling="fixed-work">
<task id="28" name="Grund Architektur" note="" work="662400" start="20190423T000000Z" end="20190612T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
</task>
<task id="30" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="29" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
</task>
</task>
<task id="31" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="30" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="11" type="FS"/>
</predecessors>
<task id="32" name="Dateien speichern/verwalten" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<task id="31" name="Dateien speichern/verwalten" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<constraint type="must-start-on" time="20190423T000000Z"/>
</task>
<task id="33" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<task id="32" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<constraint type="must-start-on" time="20190423T000000Z"/>
</task>
<task id="34" name="Suche implementieren" note="" work="86400" start="20190504T170000Z" end="20190511T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="32" type="FS"/>
</predecessors>
</task>
<task id="35" name="Hauptlogik implementieren" note="Haupts&#xE4;chlich Glue-Code&#10;&#10;" work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="33" type="FS"/>
<predecessor id="1" predecessor-id="32" type="FS"/>
</predecessors>
</task>
</task>
</task>
<task id="36" name="End of Implementation" note="" work="0" start="20190511T170000Z" end="20190511T170000Z" work-start="20190511T170000Z" percent-complete="0" priority="0" type="milestone" scheduling="fixed-work">
<task id="33" name="Suche implementieren" note="" work="86400" start="20190504T170000Z" end="20190511T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="31" type="FS"/>
<predecessor id="1" predecessor-id="22" type="FS"/>
</predecessors>
</task>
<task id="34" name="Hauptlogik implementieren" note="Haupts&#xE4;chlich Glue-Code&#10;&#10;" work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="32" type="FS"/>
<predecessor id="1" predecessor-id="31" type="FS"/>
</predecessors>
</task>
</task>
</task>
<task id="35" name="End of Implementation" note="" work="0" start="20190612T170000Z" end="20190612T170000Z" work-start="20190612T170000Z" percent-complete="0" priority="0" type="milestone" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="30" type="FS"/>
<predecessor id="1" predecessor-id="21" type="FS"/>
<predecessor id="1" predecessor-id="20" type="FS"/>
</predecessors>
</task>
<task id="37" name="Abnahme" note="" work="374400" start="20190511T170000Z" end="20190611T170000Z" work-start="20190514T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="38" name="Modultests [M]" note="" work="115200" start="20190511T170000Z" end="20190521T170000Z" work-start="20190514T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="36" name="Abnahme" note="" work="115200" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="37" name="Modultests [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="36" type="FS"/>
<predecessor id="1" predecessor-id="35" type="FS"/>
</predecessors>
</task>
<task id="39" name="Integrationstests [M]" note="" work="115200" start="20190521T170000Z" end="20190529T170000Z" work-start="20190522T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="38" name="Integrationstests [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="38" type="FS"/>
<predecessor id="1" predecessor-id="35" type="FS"/>
</predecessors>
</task>
<task id="40" name="Systemtests [M]" note="" work="115200" start="20190529T170000Z" end="20190608T170000Z" work-start="20190601T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="39" name="Systemtests [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="39" type="FS"/>
<predecessor id="1" predecessor-id="35" type="FS"/>
</predecessors>
</task>
<task id="41" name="Akzeptanztest [M]" note="" work="28800" start="20190608T170000Z" end="20190611T170000Z" work-start="20190611T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<task id="40" name="Akzeptanztest [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
<predecessors>
<predecessor id="1" predecessor-id="40" type="FS"/>
<predecessor id="1" predecessor-id="35" type="FS"/>
</predecessors>
</task>
</task>
@ -186,16 +182,17 @@
<resource id="4" name="Lukas" short-name="L" type="1" units="0" email="" note="" std-rate="0"/>
</resources>
<allocations>
<allocation task-id="33" resource-id="1" units="100"/>
<allocation task-id="29" resource-id="1" units="100"/>
<allocation task-id="32" resource-id="1" units="100"/>
<allocation task-id="8" resource-id="1" units="100"/>
<allocation task-id="13" resource-id="1" units="100"/>
<allocation task-id="11" resource-id="1" units="100"/>
<allocation task-id="5" resource-id="1" units="50"/>
<allocation task-id="29" resource-id="2" units="50"/>
<allocation task-id="28" resource-id="2" units="50"/>
<allocation task-id="8" resource-id="2" units="10"/>
<allocation task-id="17" resource-id="2" units="100"/>
<allocation task-id="5" resource-id="2" units="50"/>
<allocation task-id="29" resource-id="3" units="50"/>
<allocation task-id="28" resource-id="3" units="50"/>
<allocation task-id="15" resource-id="3" units="100"/>
<allocation task-id="12" resource-id="3" units="100"/>
<allocation task-id="10" resource-id="3" units="100"/>

View File

@ -0,0 +1,38 @@
#Statusbericht KW23 - TextureSync
Sehr geehrter Herr Nikolaropoulos,
hier der Statusbericht der vergangenen Woche TextureSync.
#Vergangene Arbeitswoche
* Robin Willmann erstelle die Netzwerk-Schnittstelle für den Client
* Jannik Seiler und Hendrik Schutter erstellten die Logik des Clients
* Lukas Fürderer erstellt einen Demo-Datensatz mit 1000 Texturen welche die Metadaten auf dem Bild darstellen
* Robin Willmann erstellt das Wunschkriterium "Autoconnect" welches den Server automatisch findet
#Nächste Arbeitswoche
* Texturen ändern und Änderungen an den Server schicken
* Alle Texturen beim Start anzeigen
* Gefunden Texturen sofort anzeigen, nicht erst wenn alle gefunden sind
* UI Style der MainView anpassen
* Erste gefundene Textur initial ausgewählt
* Beginn der Test des Clients und der Server-Client Kommunikation
* Erstellen der Kurzanleitung
#Aktuelle Informationen über das Projekt
* Website mit aktuellem Projektplan: https://planner.mosad.xyz/TextureSync.html
* Repository mit Code und Dokumenten: https://git.mosad.xyz/localhorst/TextureSync
Sollten Sie Fragen haben, können Sie sich gern bei uns melden.
Mit freundlichen Grüßen
Hendrik Schutter und Team TextureSync

View File

@ -1,6 +1,6 @@
[package]
name = "texture-sync-server"
version = "0.1.0"
version = "1.0.0"
authors = ["CodeSteak <codesteak@shellf.art>"]
edition = "2018"

View File

@ -24,6 +24,8 @@ use std::path::*;
fn main() -> std::io::Result<()> {
lovecraft::invoke();
println!("\n\n\t=== TextureSync Server {} ===\t\n\n\n", env!("CARGO_PKG_VERSION"));
let data_path = Path::new("./data");
println!("loading files from {:?}", data_path);
let server_state = ServerState::new(data_path)?;
@ -35,6 +37,10 @@ fn main() -> std::io::Result<()> {
network_conf.listen_addr, network_conf.port
);
match self::protocol::start_autoconnect_server_async(&network_conf) {
Ok(_) => println!("Started Autoconnect Server"),
Err(e) => println!("Starting Autoconnect Server failed: {:?}", e),
}
self::protocol::listen_forever(server_state, &network_conf)?;
Ok(())

View File

@ -0,0 +1,55 @@
use super::ProtocolConfig;
use std::io;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use std::net::UdpSocket;
use std::str::FromStr;
use std::thread;
pub fn start_autoconnect_server_async(cfg: &ProtocolConfig) -> io::Result<()> {
let cfg = cfg.clone();
let multi_sock = UdpSocket::bind((cfg.listen_addr.as_str(), cfg.port))?;
multi_sock.join_multicast_v6(
&Ipv6Addr::from_str(&cfg.autoconnect_multicastv6_addr).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Configured IPv6 addr. is invalid.",
)
})?,
0,
)?;
thread::spawn(move || {
let mut buffer = vec![0u8; 8096];
loop {
match multi_sock.recv_from(&mut buffer) {
Ok((pkg_size, sender)) => {
let _ = respond(&multi_sock, &buffer[0..pkg_size], &sender, &cfg);
}
Err(e) => {
println!("[Autoconnect] failed with : {:?}", e);
break;
}
}
}
});
Ok(())
}
fn respond(
sock: &UdpSocket,
input: &[u8],
sender: &SocketAddr,
cfg: &ProtocolConfig,
) -> io::Result<()> {
if input == b"TextureSync" {
sock.send_to(&u16::to_be_bytes(cfg.port), sender)?;
println!("[Autoconnect] Got request from : {}", sender);
}
Ok(())
}

View File

@ -16,8 +16,9 @@ where
// set timeouts.
// We ignore errors here, so they will be caught in the clients thread.
let _ = connection.as_mut().map(|stream| {
stream.set_read_timeout(Duration::from_secs(config.read_timeout_s).into())?;
stream.set_write_timeout(Duration::from_secs(config.write_timeout_s).into())?;
let _ = stream.set_read_timeout(Duration::from_secs(config.read_timeout_s).into());
let _ = stream.set_write_timeout(Duration::from_secs(config.write_timeout_s).into());
stream
});
let handler = handler.clone();

View File

@ -1,6 +1,9 @@
mod implementation;
pub use self::implementation::*;
mod autoconnect;
pub use self::autoconnect::start_autoconnect_server_async;
use std::fmt::Display;
use crate::model::*;
@ -53,6 +56,7 @@ pub struct ProtocolConfig {
pub read_timeout_s: u64,
pub write_timeout_s: u64,
pub listen_addr: String,
pub autoconnect_multicastv6_addr: String,
}
impl ProtocolConfig {
@ -68,6 +72,7 @@ impl Default for ProtocolConfig {
read_timeout_s: 60,
write_timeout_s: 75,
listen_addr: "::".to_owned(),
autoconnect_multicastv6_addr: "ff02::dd42:c0fe".to_owned(),
}
}
}

2
testdata/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin
outputdir

22
testdata/create.sh vendored Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# use the correct working directory
cd $(dirname "$0")
# compile java code
mkdir -p bin
javac -sourcepath src -d bin src/creation/Creator.java
# recreate output dir
rm -rf outputdir
mkdir -p outputdir/data/textures
# create textures
java -classpath bin creation.Creator
# place a server binary in the output directory
(
cd ../server/texture-sync-server
cargo build --release
)
cp ../server/texture-sync-server/target/release/texture-sync-server ./outputdir/

184
testdata/src/creation/Creator.java vendored Normal file
View File

@ -0,0 +1,184 @@
package creation;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.UUID;
import javax.imageio.ImageIO;
public class Creator {
public static void main(String[] args) {
Random r = new Random(1);
String basedir = "outputdir/data";
String[] names = NameCreator.generateNames(r);
try {
FileOutputStream collectionOutput = new FileOutputStream(basedir + "/collection.json");
PrintWriter collectionWriter = new PrintWriter(collectionOutput);
collectionWriter.write("{\n \"textures\": [\n ");
boolean first = true;
int i=1;
for (String name : names) {
if (first) {
first = false;
} else {
collectionWriter.write(",\n ");
}
System.out.println("Creating " + name + " (texture " + i + " of " + names.length + ")");
storeImage(name, basedir, r, collectionWriter);
i++;
}
collectionWriter.write("\n ]\n}\n");
collectionWriter.close();
collectionOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static boolean randBool(Random r, double probability) {
return r.nextDouble() < probability;
}
private static void storeImage(String name, String outputdir, Random r, PrintWriter collectionWriter)
throws IOException {
int size = 256 * (1 << r.nextInt(6));
NamedColor[] shuffling = Dictionary.colors.clone();
NamedColor[] mycolors = new NamedColor[3];
for (int i = 0; i < 3; i++) {
int otherPos = r.nextInt(7 - i) + i;
mycolors[i] = shuffling[otherPos];
shuffling[otherPos] = shuffling[i];
}
MyDate date = randomDate(r);
boolean grey = randBool(r, 0.01);
boolean border = randBool(r, 0.1);
boolean bold = randBool(r, 0.03);
boolean italic = randBool(r, 0.07);
BufferedImage img = createImage(size, name, mycolors, date, grey, border, bold, italic);
File tmpLocation = new File(outputdir + "/tempimage");
ImageIO.write(img, name.substring(name.length() - 3), tmpLocation);
String hash = hashFile(tmpLocation);
String ending = name.substring(name.length() - 3);
if (ending.equals("jpg")) {
ending = "jpeg";
}
tmpLocation.renameTo(new File(outputdir + "/textures/" + hash));
String tags = mycolors[0].name + "\", \"" + mycolors[1].name + "\", \"" + mycolors[2].name;
if (grey) {
tags += "\", \"grey";
}
if (border) {
tags += "\", \"border";
}
if (bold) {
tags += "\", \"bold";
}
if (italic) {
tags += "\", \"italic";
}
collectionWriter.write("{\n");
collectionWriter.write(" \"id\": \"" + UUID.randomUUID() + "\",\n");
collectionWriter.write(" \"name\": \"" + name + "\",\n");
collectionWriter.write(" \"tags\": [\"" + tags + "\"],\n");
collectionWriter.write(" \"format\": \"" + ending + "\",\n");
collectionWriter.write(" \"resolution\": [" + size + ", " + size + "],\n");
collectionWriter.write(" \"added_on\": " + date.asJsonArray() + ",\n");
collectionWriter.write(" \"texture_hash\": \"" + hash + "\"\n");
collectionWriter.write(" }");
}
private static MyDate randomDate(Random r) {
int back = (int) (-300 * Math.log(r.nextDouble()));
return new MyDate(3, 6, 2019).ago(back);
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
private static String hashFile(File f) throws IOException {
byte[] data = Files.readAllBytes(f.toPath());
try {
MessageDigest hash = MessageDigest.getInstance("SHA-256");
byte[] hashval = hash.digest(data);
return bytesToHex(hashval);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
private static BufferedImage createImage(int size, String name, NamedColor[] colors, MyDate date, boolean grey,
boolean border, boolean bold, boolean italic) {
BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
// background
if (grey) {
g.setColor(Color.LIGHT_GRAY);
} else {
g.setColor(Color.WHITE);
}
g.fillRect(0, 0, size, size);
// border
if (border) {
int w = size / 40;
int l = size - 2*w;
g.setColor(Color.BLACK);
g.fillRect(w, w, l, w);
g.fillRect(w, w, w, l);
g.fillRect(w, l, l, w);
g.fillRect(l, w, w, l);
}
// text
int fontstyle = Font.PLAIN;
if (bold) {
fontstyle |= Font.BOLD;
}
if (italic) {
fontstyle |= Font.ITALIC;
}
Font f = new Font("Arial", fontstyle, size / 10);
g.setFont(f);
g.setColor(Color.BLACK);
int width = g.getFontMetrics().stringWidth(name);
int basey = size / 2 + g.getFontMetrics().getAscent();
int textheight = g.getFontMetrics().getHeight();
g.drawString(name, (size - width) / 2, basey);
g.drawString(size + " x " + size, (size - width) / 2, basey + textheight);
g.drawString(date.asReadableString(), (size - width) / 2, basey + 2 * textheight);
// color dots
for (int i = 0; i < 3; i++) {
NamedColor current = colors[i];
g.setColor(current.color);
g.fillOval((1 + 3 * i) * size / 10, size * 3 / 10, size / 5, size / 5);
}
return img;
}
}

16
testdata/src/creation/Dictionary.java vendored Normal file
View File

@ -0,0 +1,16 @@
package creation;
import java.awt.Color;
public class Dictionary {
public static final String[] adjectives = { "fast", "slow", "long", "short", "fat", "big", "small", "angry",
"awful", "calm", "clever", "crazy", "dirty", "excited", "evil", "kind", "lucky", "poor", "shy", "soft",
"tall", };
public static final String[] animals = { "bat", "bee", "camel", "cat", "chicken", "cod", "deer", "dog", "duck",
"fly", "fox", "frog", "horse", "koala", "lion", "mouse", "owl", "pig", "rabbit", "rat", "tiger", "turtle",
"wolf", "zebra" };
public static final NamedColor[] colors = { new NamedColor("red", Color.RED),
new NamedColor("orange", Color.ORANGE), new NamedColor("yellow", Color.YELLOW),
new NamedColor("green", Color.GREEN), new NamedColor("blue", Color.BLUE),
new NamedColor("magenta", Color.MAGENTA), new NamedColor("black", Color.BLACK), };
}

66
testdata/src/creation/MyDate.java vendored Normal file
View File

@ -0,0 +1,66 @@
package creation;
public class MyDate {
public final int day, month, year;
public MyDate(int day, int month, int year) {
if (!isValid(day, month, year)) {
throw new IllegalArgumentException("Invalid date");
}
this.day = day;
this.month = month;
this.year = year;
}
public String asJsonArray() {
return "[" + year + ", " + month + ", " + day + "]";
}
public String asReadableString() {
return twoDigit(day) + "." + twoDigit(month) + "." + twoDigit(year);
}
public MyDate previous() {
if (isValid(day - 1, month, year)) {
return new MyDate(day - 1, month, year);
}
for (int possibleDay = 31; possibleDay >= 28; possibleDay--) {
if (isValid(possibleDay, month - 1, year)) {
return new MyDate(possibleDay, month - 1, year);
}
}
return new MyDate(31, 12, year - 1);
}
public MyDate ago(int number) {
MyDate d = this;
for (int i=0; i<number; i++) {
d = d.previous();
}
return d;
}
private static String twoDigit(int x) {
String s = ""+x;
while (s.length() < 2) {
s = "0"+s;
}
return s;
}
private boolean isValid(int day, int month, int year) {
int[] monthLengths = new int[] { 31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month < 1 || month > 12) {
return false;
}
int length = monthLengths[month - 1];
if (day < 1 || day > length) {
return false;
}
return true;
}
private boolean isLeapYear(int year) {
return (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
}
}

35
testdata/src/creation/NameCreator.java vendored Normal file
View File

@ -0,0 +1,35 @@
package creation;
import java.util.ArrayList;
import java.util.Random;
public class NameCreator {
private static void shuffle(ArrayList<String> list, Random r) {
for (int low=0; low<list.size()-1; low++) {
int high = r.nextInt(list.size() - low) + low;
// swap
String sLow = list.get(low);
String sHigh = list.get(high);
list.set(low, sHigh);
list.set(high, sLow);
}
}
public static String[] generateNames(Random r) {
ArrayList<String> names = new ArrayList<>();
for (String ending : new String[] {"jpg", "png"}) {
for (String animal : Dictionary.animals) {
for (String adjective : Dictionary.adjectives) {
String name = adjective + "-" + animal + "." + ending;
names.add(name);
}
}
}
shuffle(names, r);
while (names.size() > 1000) {
names.remove(names.size() - 1);
}
return names.toArray(new String[1000]);
}
}

13
testdata/src/creation/NamedColor.java vendored Normal file
View File

@ -0,0 +1,13 @@
package creation;
import java.awt.Color;
public class NamedColor {
public final String name;
public final Color color;
public NamedColor(String name, Color color) {
this.name = name;
this.color = color;
}
}