Compare commits
100 Commits
cf37e579ad
...
1.0.0
Author | SHA1 | Date | |
---|---|---|---|
8c6a43fc22
|
|||
7f4182bfad
|
|||
5fff4023f9
|
|||
dfad679e6e | |||
391dfe4282
|
|||
225316abb9 | |||
f5dbcc0134 | |||
cac4807357 | |||
33fa741a2a | |||
37406acd98 | |||
56c62422cc | |||
bc43b33536 | |||
1ac13f80d5 | |||
8dfbbae559 | |||
cc03b32ade | |||
a2f5b65e30 | |||
f094a68b04 | |||
b5545e1bae | |||
9a9a1e0056 | |||
0f23749001 | |||
c1dbd8ffb7 | |||
ebc6449e48 | |||
bef56f978c | |||
50ea54c8d6 | |||
24b9755089 | |||
6fbfa050b3 | |||
9a3524143a | |||
10076fcef2 | |||
6219ad93d0 | |||
8109782e4e | |||
c2d01760c7 | |||
2cac52b48d | |||
35f8145112 | |||
e26d54e96f | |||
7e3ce9ce56
|
|||
9975364621 | |||
491578806e | |||
b1052fa894
|
|||
36c4805061
|
|||
ee7fbae29c | |||
07f2682a2d
|
|||
efc28455c1 | |||
5d223ba72b
|
|||
15cbbb1bac | |||
08840c4c2b | |||
6b6ef67122
|
|||
4a213afdf5 | |||
36a8bccfe3 | |||
4f35293151 | |||
29354199cc | |||
f4db466b37 | |||
19eb6660e1 | |||
4e8b4c364b | |||
f22a9eb260
|
|||
9ae50df1a4
|
|||
ce11d2e263
|
|||
3f18760a74 | |||
89ef736954 | |||
3e1c46db81
|
|||
be11f35159 | |||
39c358c00f
|
|||
61980905e3 | |||
fc5db5d051 | |||
4c2e1c2917 | |||
a82cef599c
|
|||
86008828f5 | |||
c1ec000b98 | |||
ed558106e5 | |||
da153091bd
|
|||
9200e9277a
|
|||
40bfc73ba8 | |||
b1984ac8dd | |||
1c1f12ecaf
|
|||
d5b7fd0f94
|
|||
2c9a23ec8a
|
|||
f45d807536 | |||
ed344cbd49
|
|||
8e7ba1cb46 | |||
b2a84db5a7 | |||
71f50b832e | |||
0f8d2ba256 | |||
b0a1844b16 | |||
594f083a8d
|
|||
90b21ca3d1 | |||
db8178f4c4 | |||
4cd4481182 | |||
3c2984a404 | |||
c55182d6cc | |||
e540e58161 | |||
727558d867 | |||
adc8725a36 | |||
99ef2a8edc | |||
de47e99852
|
|||
d7a67c497e
|
|||
8d387250e7 | |||
e32766e581 | |||
53dcebecde
|
|||
c80233155f
|
|||
857f35e77e | |||
e78e7e3a59 |
@ -15,21 +15,8 @@ buildscript {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
|
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
|
||||||
}
|
id "com.github.johnrengelman.shadow" version "5.0.0"
|
||||||
|
id "application"
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
@ -37,4 +24,20 @@ compileKotlin {
|
|||||||
}
|
}
|
||||||
compileTestKotlin {
|
compileTestKotlin {
|
||||||
kotlinOptions.jvmTarget = "1.8"
|
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'
|
||||||
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
rootProject.name = 'texuresync_client'
|
rootProject.name = 'texturesync_client'
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,17 +1,36 @@
|
|||||||
package org.hso.texturesyncclient.app
|
package org.hso.texturesyncclient.app
|
||||||
|
|
||||||
import org.hso.texturesyncclient.controller.RootController
|
import javafx.scene.image.Image
|
||||||
import org.hso.texturesyncclient.view.importView.ImportView
|
import javafx.stage.Stage
|
||||||
import org.hso.texturesyncclient.view.mainView.MainView
|
import org.hso.texturesyncclient.controller.SettingsController
|
||||||
import org.hso.texturesyncclient.view.startupView.StartupView
|
import org.hso.texturesyncclient.view.startupView.StartupView
|
||||||
|
import org.hso.texturesyncclient.view.startupView.StartupViewController
|
||||||
import tornadofx.App
|
import tornadofx.App
|
||||||
|
|
||||||
class Main: App(StartupView::class){
|
class Main : App(StartupView::class) {
|
||||||
|
|
||||||
val controller = RootController()
|
private val svc: StartupViewController by inject()
|
||||||
|
|
||||||
init {
|
override fun start(stage: Stage) {
|
||||||
// TODO get saved IP address, if found try to connect, else show StartupView
|
|
||||||
|
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package org.hso.texturesyncclient.controller
|
|
||||||
|
|
||||||
class NetworkController {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,7 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun getStreams(): Pair<DataInputStream, DataOutputStream> {
|
private fun <R> withStreams(retry: Boolean = true, inner: (DataInputStream, DataOutputStream) -> R): R {
|
||||||
val i: DataInputStream
|
val i: DataInputStream
|
||||||
val o: DataOutputStream
|
val o: DataOutputStream
|
||||||
|
|
||||||
@ -40,240 +40,253 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
|||||||
o = output!!
|
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)
|
@Throws(IOException::class, ConnectionException::class)
|
||||||
@Synchronized
|
|
||||||
fun ping() {
|
fun ping() {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
|
||||||
val obj = JsonObject()
|
val obj = JsonObject()
|
||||||
obj.add("ping", JsonObject())
|
obj.add("ping", JsonObject())
|
||||||
|
|
||||||
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
|
is JsonPackage -> return@withStreams
|
||||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||||
else -> throw RuntimeException("Unreachable")
|
else -> throw RuntimeException("Unreachable")
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class)
|
@Throws(IOException::class, ConnectionException::class)
|
||||||
@Synchronized
|
|
||||||
fun query(query: Array<String>): Array<Texture> {
|
fun query(query: Array<String>): Array<Texture> {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
|
||||||
val obj = JsonObject()
|
val obj = JsonObject()
|
||||||
obj.add("query", {
|
obj.add("query", {
|
||||||
val inner = JsonObject()
|
val inner = JsonObject()
|
||||||
inner.add("query", {
|
inner.add("query", {
|
||||||
val array = JsonArray()
|
val array = JsonArray()
|
||||||
for (queryString in query) {
|
for (queryString in query) {
|
||||||
array.add(queryString)
|
array.add(queryString)
|
||||||
}
|
}
|
||||||
array
|
array
|
||||||
|
}())
|
||||||
|
inner
|
||||||
}())
|
}())
|
||||||
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 -> {
|
is JsonPackage -> {
|
||||||
try {
|
try {
|
||||||
return Gson().fromJson<Array<InternalTexture>>(pkg.content, Array<InternalTexture>::class.java)
|
return@withStreams Gson().fromJson<Array<InternalTexture>>(
|
||||||
.map { tex ->
|
pkg.content,
|
||||||
tex.toTexture()
|
Array<InternalTexture>::class.java
|
||||||
}.toTypedArray()
|
)
|
||||||
} catch (e: JsonSyntaxException) {
|
.map { tex ->
|
||||||
throw ConnectionInvalidJsonException()
|
tex.toTexture()
|
||||||
|
}.toTypedArray()
|
||||||
|
} catch (e: JsonSyntaxException) {
|
||||||
|
throw ConnectionInvalidJsonException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
|
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||||
|
else -> throw RuntimeException("Unreachable")
|
||||||
}
|
}
|
||||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
|
||||||
else -> throw RuntimeException("Unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class)
|
@Throws(IOException::class, ConnectionException::class)
|
||||||
@Synchronized
|
|
||||||
fun getTextureById(id: UUID): Texture? {
|
fun getTextureById(id: UUID): Texture? {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
|
||||||
val obj = JsonObject()
|
|
||||||
obj.add("get_texture", {
|
|
||||||
val inner = JsonObject()
|
|
||||||
inner.addProperty("id", id.toString())
|
|
||||||
inner
|
|
||||||
}())
|
|
||||||
|
|
||||||
JsonPackage(obj).write(io.second)
|
val obj = JsonObject()
|
||||||
|
obj.add("get_texture", {
|
||||||
|
val inner = JsonObject()
|
||||||
|
inner.addProperty("id", id.toString())
|
||||||
|
inner
|
||||||
|
}())
|
||||||
|
|
||||||
when (val pkg = Package.read(io.first)) {
|
JsonPackage(obj).write(o)
|
||||||
is JsonPackage -> {
|
|
||||||
return if (pkg.content.isJsonNull) {
|
when (val pkg = Package.read(i)) {
|
||||||
null
|
is JsonPackage -> {
|
||||||
} else {
|
return@withStreams if (pkg.content.isJsonNull) {
|
||||||
try {
|
null
|
||||||
Gson()
|
} else {
|
||||||
.fromJson<InternalTexture>(pkg.content, InternalTexture::class.java)
|
try {
|
||||||
.toTexture()
|
Gson()
|
||||||
} catch (e: JsonSyntaxException) {
|
.fromJson<InternalTexture>(pkg.content, InternalTexture::class.java)
|
||||||
throw ConnectionInvalidJsonException()
|
.toTexture()
|
||||||
|
} catch (e: JsonSyntaxException) {
|
||||||
|
throw ConnectionInvalidJsonException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
|
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||||
|
else -> throw RuntimeException("Unreachable")
|
||||||
}
|
}
|
||||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
|
||||||
else -> throw RuntimeException("Unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class)
|
@Throws(IOException::class, ConnectionException::class)
|
||||||
@Synchronized
|
|
||||||
fun getTextureByName(name: String): Texture? {
|
fun getTextureByName(name: String): Texture? {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
|
||||||
val obj = JsonObject()
|
val obj = JsonObject()
|
||||||
obj.add("get_texture", {
|
obj.add("get_texture", {
|
||||||
val inner = JsonObject()
|
val inner = JsonObject()
|
||||||
inner.addProperty("name", name)
|
inner.addProperty("name", name)
|
||||||
inner
|
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 -> {
|
is JsonPackage -> {
|
||||||
return if (pkg.content.isJsonNull) {
|
return@withStreams if (pkg.content.isJsonNull) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
Gson()
|
Gson()
|
||||||
.fromJson<InternalTexture>(pkg.content, InternalTexture::class.java)
|
.fromJson<InternalTexture>(pkg.content, InternalTexture::class.java)
|
||||||
.toTexture()
|
.toTexture()
|
||||||
} catch (e: JsonSyntaxException) {
|
} catch (e: JsonSyntaxException) {
|
||||||
throw ConnectionInvalidJsonException()
|
throw ConnectionInvalidJsonException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
|
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||||
|
else -> throw RuntimeException("Unreachable")
|
||||||
}
|
}
|
||||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
|
||||||
else -> throw RuntimeException("Unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class)
|
@Throws(IOException::class, ConnectionException::class)
|
||||||
@Synchronized
|
|
||||||
fun getTextureFile(hash: Sha256): ByteArray {
|
fun getTextureFile(hash: Sha256): ByteArray {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
|
||||||
val obj = JsonObject()
|
val obj = JsonObject()
|
||||||
obj.add("get_texture_file", {
|
obj.add("get_texture_file", {
|
||||||
val inner = JsonObject()
|
val inner = JsonObject()
|
||||||
inner.addProperty("texture_hash", hash.toString())
|
inner.addProperty("texture_hash", hash.toString())
|
||||||
inner
|
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 JsonPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
is BinaryPackage -> return pkg.content
|
is BinaryPackage -> return@withStreams pkg.content
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||||
else -> throw RuntimeException("Unreachable")
|
else -> throw RuntimeException("Unreachable")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class)
|
@Throws(IOException::class, ConnectionException::class)
|
||||||
@Synchronized
|
|
||||||
fun getTexturePreview(hash: Sha256): Image {
|
fun getTexturePreview(hash: Sha256): Image {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
val obj = JsonObject()
|
||||||
|
obj.add("get_texture_preview", {
|
||||||
|
val inner = JsonObject()
|
||||||
|
inner.addProperty("texture_hash", hash.toString())
|
||||||
|
inner.addProperty("desired_format", "jpeg")
|
||||||
|
inner
|
||||||
|
}())
|
||||||
|
|
||||||
val obj = JsonObject()
|
JsonPackage(obj).write(o)
|
||||||
obj.add("get_texture_preview", {
|
|
||||||
val inner = JsonObject()
|
|
||||||
inner.addProperty("texture_hash", hash.toString())
|
|
||||||
inner.addProperty("desired_format", "jpeg")
|
|
||||||
inner
|
|
||||||
}())
|
|
||||||
|
|
||||||
JsonPackage(obj).write(io.second)
|
when (val pkg = Package.read(i)) {
|
||||||
|
is JsonPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
when (val pkg = Package.read(io.first)) {
|
is BinaryPackage -> {
|
||||||
is JsonPackage -> throw ConnectionUnexpectedPacketException()
|
return@withStreams Image(ByteArrayInputStream(pkg.content))
|
||||||
is BinaryPackage -> {
|
}
|
||||||
return Image(ByteArrayInputStream(pkg.content))
|
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||||
|
else -> throw RuntimeException("Unreachable")
|
||||||
}
|
}
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
|
||||||
else -> throw RuntimeException("Unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||||
@Synchronized
|
|
||||||
private fun replaceTexture(old: Texture?, new: Texture?, image: ByteArray?) {
|
private fun replaceTexture(old: Texture?, new: Texture?, image: ByteArray?) {
|
||||||
val io = getStreams()
|
return withStreams { i, o ->
|
||||||
|
|
||||||
val obj = JsonObject()
|
|
||||||
obj.add("replace_texture", {
|
|
||||||
val inner = JsonObject()
|
|
||||||
if (old != null) {
|
|
||||||
inner.add("old", Gson().toJsonTree(InternalTexture(old), InternalTexture::class.java))
|
|
||||||
} else {
|
|
||||||
inner.add("old", null)
|
|
||||||
}
|
|
||||||
if (new != null) {
|
|
||||||
inner.add("new", Gson().toJsonTree(InternalTexture(new), InternalTexture::class.java))
|
|
||||||
} else {
|
|
||||||
inner.add("new", null)
|
|
||||||
}
|
|
||||||
inner
|
|
||||||
}())
|
|
||||||
|
|
||||||
JsonPackage(obj).write(io.second)
|
val obj = JsonObject()
|
||||||
|
obj.add("replace_texture", {
|
||||||
|
val inner = JsonObject()
|
||||||
|
if (old != null) {
|
||||||
|
inner.add("old", Gson().toJsonTree(InternalTexture(old), InternalTexture::class.java))
|
||||||
|
} else {
|
||||||
|
inner.add("old", null)
|
||||||
|
}
|
||||||
|
if (new != null) {
|
||||||
|
inner.add("new", Gson().toJsonTree(InternalTexture(new), InternalTexture::class.java))
|
||||||
|
} else {
|
||||||
|
inner.add("new", null)
|
||||||
|
}
|
||||||
|
inner
|
||||||
|
}())
|
||||||
|
|
||||||
when (val pkg = Package.read(io.first)) {
|
JsonPackage(obj).write(o)
|
||||||
is JsonPackage -> {
|
|
||||||
if (pkg.content == JsonPrimitive(true)) {
|
|
||||||
// everthing is fine!
|
|
||||||
return
|
|
||||||
} else if (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 pkg = Package.read(i)) {
|
||||||
when (val ipkg = Package.read(io.first)) {
|
is JsonPackage -> {
|
||||||
is JsonPackage -> {
|
when {
|
||||||
if (ipkg.content != JsonPrimitive(true)) {
|
pkg.content == JsonPrimitive(true) -> // everthing is fine!
|
||||||
// Protokoll Assertion failed
|
return@withStreams
|
||||||
throw ConnectionUnexpectedPacketException()
|
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(o)
|
||||||
|
when (val ipkg = Package.read(i)) {
|
||||||
|
is JsonPackage -> {
|
||||||
|
if (ipkg.content != JsonPrimitive(true)) {
|
||||||
|
// Protokoll Assertion failed
|
||||||
|
throw ConnectionUnexpectedPacketException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||||
|
is ErrorPackage -> throw ConnectionErrorException(ipkg)
|
||||||
|
else -> throw RuntimeException("Unreachable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
else -> {
|
||||||
is ErrorPackage -> throw ConnectionErrorException(ipkg)
|
ErrorPackage(404, "Texture not found!").write(o)
|
||||||
else -> throw RuntimeException("Unreachable")
|
close() // gets re-opened on next request.
|
||||||
|
throw IllegalArgumentException("Image Argument was needed.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ErrorPackage(404, "Texture not found!").write(io.second)
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
|
||||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
|
||||||
else -> throw RuntimeException("Unreachable")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||||
@Synchronized
|
|
||||||
fun uploadTexture(texture: Texture, image: ByteArray) {
|
fun uploadTexture(texture: Texture, image: ByteArray) {
|
||||||
if (texture.textureHash != Sha256(image)) {
|
if (texture.textureHash != Sha256(image)) {
|
||||||
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
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)
|
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||||
@Synchronized
|
|
||||||
fun updateTexture(old: Texture, new: Texture, image: ByteArray) {
|
fun updateTexture(old: Texture, new: Texture, image: ByteArray) {
|
||||||
if (new.textureHash != Sha256(image)) {
|
if (new.textureHash != Sha256(image)) {
|
||||||
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
||||||
@ -293,26 +305,28 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||||
@Synchronized
|
|
||||||
fun deleteTexture(texture: Texture) {
|
fun deleteTexture(texture: Texture) {
|
||||||
replaceTexture(texture, null, null)
|
replaceTexture(texture, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@Synchronized
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
if (output != null) {
|
try {
|
||||||
output!!.close()
|
if (output != null) {
|
||||||
output = null
|
output!!.close()
|
||||||
}
|
output = null
|
||||||
if (input != null) {
|
}
|
||||||
input!!.close()
|
if (input != null) {
|
||||||
input = null
|
input!!.close()
|
||||||
}
|
input = null
|
||||||
if (socket != null) {
|
}
|
||||||
socket!!.close()
|
if (socket != null) {
|
||||||
socket = null
|
socket!!.close()
|
||||||
|
socket = null
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ internal data class InternalTexture(
|
|||||||
resolution = arrayOf(tex.resolution.first, tex.resolution.second),
|
resolution = arrayOf(tex.resolution.first, tex.resolution.second),
|
||||||
added_on = arrayOf(
|
added_on = arrayOf(
|
||||||
tex.addedOn.get(Calendar.YEAR), //
|
tex.addedOn.get(Calendar.YEAR), //
|
||||||
tex.addedOn.get(Calendar.MONTH), //
|
tex.addedOn.get(Calendar.MONTH) + 1, //
|
||||||
tex.addedOn.get(Calendar.DAY_OF_MONTH)
|
tex.addedOn.get(Calendar.DAY_OF_MONTH)
|
||||||
),
|
),
|
||||||
texture_hash = tex.textureHash.toString()
|
texture_hash = tex.textureHash.toString()
|
||||||
@ -50,7 +50,7 @@ internal data class InternalTexture(
|
|||||||
else -> throw ConnectionInvalidJsonException()
|
else -> throw ConnectionInvalidJsonException()
|
||||||
},
|
},
|
||||||
resolution = Pair(resolution[0], resolution[1]),
|
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)
|
textureHash = Sha256(texture_hash)
|
||||||
)
|
)
|
||||||
} catch (e: Exception) { // i Know, but no time :[]
|
} catch (e: Exception) { // i Know, but no time :[]
|
||||||
|
@ -6,7 +6,7 @@ import java.lang.Exception
|
|||||||
|
|
||||||
sealed class ConnectionException(override val message: String) : Exception(message)
|
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") {
|
ConnectionException("$errorCode $errorMessage") {
|
||||||
internal constructor(err: ErrorPackage) : this(err.code, err.message)
|
internal constructor(err: ErrorPackage) : this(err.code, err.message)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ enum class TextureFormat {
|
|||||||
data class Texture(
|
data class Texture(
|
||||||
val id : UUID,
|
val id : UUID,
|
||||||
val name : String,
|
val name : String,
|
||||||
val tags : Array<String>,
|
var tags : Array<String>,
|
||||||
val format : TextureFormat,
|
val format : TextureFormat,
|
||||||
val resolution : Pair<Int, Int>,
|
val resolution : Pair<Int, Int>,
|
||||||
val addedOn : Calendar,
|
val addedOn : Calendar,
|
||||||
|
@ -12,30 +12,65 @@ import javafx.scene.layout.BackgroundFill
|
|||||||
import javafx.scene.layout.CornerRadii
|
import javafx.scene.layout.CornerRadii
|
||||||
import javafx.scene.layout.VBox
|
import javafx.scene.layout.VBox
|
||||||
import javafx.scene.paint.Paint
|
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 image = ImageView()
|
||||||
private var label = Label()
|
private var label = Label()
|
||||||
private var contextMenu = ContextMenu()
|
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 {
|
init {
|
||||||
|
super.setPadding(Insets(5.0, 5.0, 5.0, 5.0))
|
||||||
super.getChildren().addAll(image, label)
|
super.getChildren().addAll(image, label)
|
||||||
super.setOnContextMenuRequested { p0 -> contextMenu.show(this@GUIModel, p0.screenX, p0.screenY) }
|
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.paddingTop = 5.0
|
||||||
label.prefWidth = 128.0
|
label.prefWidth = 128.0
|
||||||
label.alignment = Pos.CENTER
|
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.fitHeight = 128.0
|
||||||
image.fitWidth = 128.0
|
image.fitWidth = 128.0
|
||||||
image.image = img
|
image.image = img
|
||||||
|
|
||||||
|
exportItem.setOnAction {
|
||||||
|
gmc.export(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteItem.setOnAction {
|
||||||
|
gmc.delete()
|
||||||
|
}
|
||||||
|
|
||||||
contextMenu.items.add(exportItem)
|
contextMenu.items.add(exportItem)
|
||||||
|
contextMenu.items.add(deleteItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ package org.hso.texturesyncclient.view.importView
|
|||||||
import com.jfoenix.controls.JFXButton
|
import com.jfoenix.controls.JFXButton
|
||||||
import com.jfoenix.controls.JFXChipView
|
import com.jfoenix.controls.JFXChipView
|
||||||
import com.jfoenix.controls.JFXTextField
|
import com.jfoenix.controls.JFXTextField
|
||||||
import javafx.collections.ListChangeListener
|
|
||||||
import javafx.geometry.Pos
|
import javafx.geometry.Pos
|
||||||
import javafx.scene.layout.Background
|
import javafx.scene.layout.Background
|
||||||
import javafx.geometry.Insets
|
import javafx.geometry.Insets
|
||||||
@ -11,24 +10,30 @@ import javafx.scene.layout.BackgroundFill
|
|||||||
import javafx.scene.layout.CornerRadii
|
import javafx.scene.layout.CornerRadii
|
||||||
import javafx.scene.paint.Paint
|
import javafx.scene.paint.Paint
|
||||||
import javafx.scene.layout.Priority
|
import javafx.scene.layout.Priority
|
||||||
|
import org.hso.texturesyncclient.view.mainView.Preview3D
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class ImportView : View() {
|
class ImportView : View("TextureSync") {
|
||||||
|
|
||||||
val tfFilePath = JFXTextField()
|
val tfFilePath = JFXTextField()
|
||||||
val tfName = JFXTextField()
|
val tfName = JFXTextField()
|
||||||
val cvTags = JFXChipView<String>()
|
val cvTags = JFXChipView<String>()
|
||||||
val btnImport = JFXButton("Importieren")
|
val btnImport = JFXButton("Importieren")
|
||||||
|
|
||||||
|
val preview = Preview3D()
|
||||||
|
|
||||||
|
private val btnBack = JFXButton("Zurück")
|
||||||
|
|
||||||
private val ivc: ImportViewController by inject()
|
private val ivc: ImportViewController by inject()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
btnImport.isVisible = false
|
btnImport.isVisible = false
|
||||||
|
preview.root.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override val root = borderpane {
|
override val root = borderpane {
|
||||||
minWidth = 1000.0
|
prefWidth = FX.primaryStage.width
|
||||||
minHeight = 500.0
|
prefHeight = FX.primaryStage.height
|
||||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||||
|
|
||||||
center = vbox(50) {
|
center = vbox(50) {
|
||||||
@ -39,6 +44,8 @@ class ImportView : View() {
|
|||||||
style = "-fx-font: 20px Verdana; -fx-text-fill: #2b7bbb;"
|
style = "-fx-font: 20px Verdana; -fx-text-fill: #2b7bbb;"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(preview)
|
||||||
|
|
||||||
vbox(20) {
|
vbox(20) {
|
||||||
hbox(10) {
|
hbox(10) {
|
||||||
add(tfFilePath)
|
add(tfFilePath)
|
||||||
@ -69,9 +76,8 @@ class ImportView : View() {
|
|||||||
vbox(5) {
|
vbox(5) {
|
||||||
alignment = Pos.CENTER
|
alignment = Pos.CENTER
|
||||||
add(btnImport)
|
add(btnImport)
|
||||||
|
add(btnBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,16 +89,17 @@ class ImportView : View() {
|
|||||||
tfName.style = "-fx-text-fill: #b15b2e;"
|
tfName.style = "-fx-text-fill: #b15b2e;"
|
||||||
tfName.promptText = "Name eingeben"
|
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
|
//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() }
|
tfFilePath.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
||||||
tfName.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
tfName.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
||||||
|
|
||||||
cvTags.chips.addListener { change: ListChangeListener.Change<out String>? ->
|
cvTags.chips.onChange {
|
||||||
ivc.validateImport()
|
ivc.validateImport()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +107,10 @@ class ImportView : View() {
|
|||||||
ivc.btnImportAction()
|
ivc.btnImportAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
btnBack.setOnAction {
|
||||||
|
ivc.btnBackAction()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,32 +1,68 @@
|
|||||||
package org.hso.texturesyncclient.view.importView
|
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 org.hso.texturesyncclient.controller.RootController
|
||||||
import tornadofx.Controller
|
import tornadofx.Controller
|
||||||
import tornadofx.FileChooserMode
|
import java.io.File
|
||||||
import tornadofx.chooseFile
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
|
||||||
class ImportViewController : Controller() {
|
class ImportViewController : Controller() {
|
||||||
|
|
||||||
private val iv = find(ImportView::class)
|
private val iv = find(ImportView::class)
|
||||||
private val rootc = find(RootController::class)
|
private val rootc = find(RootController::class)
|
||||||
|
|
||||||
fun btnFileChooserAction() {
|
private var lastImportDir: String = System.getProperty("user.home")
|
||||||
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)
|
|
||||||
|
|
||||||
if (file.isNotEmpty()) {
|
fun btnFileChooserAction() {
|
||||||
iv.tfFilePath.text = file[0].absolutePath
|
val fileChooser = FileChooser()
|
||||||
iv.tfName.text = file[0].nameWithoutExtension
|
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() {
|
fun btnImportAction() {
|
||||||
rootc.importTexture(iv.tfFilePath.text, iv.tfName.text, iv.cvTags.chips)
|
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() {
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package org.hso.texturesyncclient.view.mainView
|
package org.hso.texturesyncclient.view.mainView
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton
|
||||||
import com.jfoenix.controls.JFXChipView
|
import com.jfoenix.controls.JFXChipView
|
||||||
|
import com.jfoenix.controls.JFXTextField
|
||||||
import javafx.geometry.Insets
|
import javafx.geometry.Insets
|
||||||
import javafx.geometry.Orientation
|
import javafx.geometry.Orientation
|
||||||
import javafx.scene.control.Label
|
|
||||||
import javafx.scene.image.Image
|
import javafx.scene.image.Image
|
||||||
import javafx.scene.layout.Background
|
import javafx.scene.layout.Background
|
||||||
import javafx.scene.layout.BackgroundFill
|
import javafx.scene.layout.BackgroundFill
|
||||||
@ -11,43 +12,69 @@ import javafx.scene.layout.CornerRadii
|
|||||||
import javafx.scene.paint.Paint
|
import javafx.scene.paint.Paint
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class DetailView: View() {
|
class DetailView : View() {
|
||||||
|
|
||||||
val preview = Preview3D()
|
val preview = Preview3D()
|
||||||
val metaLabel = Label("Auflösung: 8MP\nName: Texture.png\nAndere: was anderes")
|
|
||||||
val cvTags = JFXChipView<String>()
|
val cvTags = JFXChipView<String>()
|
||||||
|
|
||||||
init {
|
val nameInfo = JFXTextField().addClass("metadata")
|
||||||
// set a default texture
|
val resolutionInfo = label().addClass("metadata")
|
||||||
preview.setTexture(Image("textures/sample_texture_1.jpg"))
|
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 {
|
override val root = form {
|
||||||
minWidth = 250.0
|
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) {
|
vbox(7) {
|
||||||
add(preview)
|
add(preview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field("Meta") {
|
field {
|
||||||
add(metaLabel)
|
add(metadataPanel)
|
||||||
}
|
}
|
||||||
|
|
||||||
field("Tags") {
|
field {
|
||||||
|
minHeight = 155.0
|
||||||
add(cvTags)
|
add(cvTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add "Import" Btn
|
field {
|
||||||
}
|
add(btnSubmit)
|
||||||
|
}
|
||||||
|
|
||||||
style {
|
|
||||||
cvTags.style = "-fx-background-color: #3c3f41; -fx-text-inner-color: #b15b2e;"
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// set a default texture
|
||||||
|
preview.setTexture(Image("icons/TextureSync_Icon_256x256.jpeg"))
|
||||||
|
btnSubmit.useMaxWidth = true
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,14 +10,10 @@ import tornadofx.*
|
|||||||
class FolderView : View("FolderView"){
|
class FolderView : View("FolderView"){
|
||||||
|
|
||||||
override val root = flowpane {
|
override val root = flowpane {
|
||||||
hgap = 10.0
|
hgap = 5.0
|
||||||
vgap = 10.0
|
vgap = 5.0
|
||||||
paddingAll = 5.0
|
paddingAll = 10.0
|
||||||
prefWidth = 750.0
|
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||||
background = Background(BackgroundFill(Paint.valueOf("#cfcfcf"), CornerRadii.EMPTY, Insets.EMPTY))
|
|
||||||
|
|
||||||
style {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,41 +1,99 @@
|
|||||||
package org.hso.texturesyncclient.view.mainView
|
package org.hso.texturesyncclient.view.mainView
|
||||||
|
|
||||||
import javafx.collections.ListChangeListener
|
import com.jfoenix.controls.JFXButton
|
||||||
import javafx.scene.image.Image
|
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.*
|
import tornadofx.*
|
||||||
|
|
||||||
class MainView : View() {
|
class MainView : View("TextureSync") {
|
||||||
|
|
||||||
|
val cvSearch = JFXChipView<String>()
|
||||||
|
private val btnImport = JFXButton("+")
|
||||||
val folderView = find(FolderView::class)
|
val folderView = find(FolderView::class)
|
||||||
val detailView = find(DetailView::class)
|
val detailView = find(DetailView::class)
|
||||||
|
|
||||||
private val mvc: MainViewController by inject()
|
private val mvc: MainViewController by inject()
|
||||||
|
|
||||||
override val root = borderpane {
|
override val root = anchorpane {
|
||||||
minWidth = 1000.0
|
|
||||||
minHeight = 500.0
|
|
||||||
|
|
||||||
left = folderView.root
|
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||||
right = detailView.root
|
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 {
|
||||||
// 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
|
// actions
|
||||||
// folderView.btn1.setOnAction {
|
cvSearch.chips.onChange {
|
||||||
// mvc.setPreview3DTexture(Image("textures/sample_texture_1.jpg"))
|
mvc.cvSearchAction(cvSearch.chips)
|
||||||
// mvc.setMeta("texture 1", "8MP", "Quelle: wikipedia")
|
|
||||||
// mvc.setTags(observableList("Stein", "Rot", "super"))
|
|
||||||
// }
|
|
||||||
|
|
||||||
detailView.cvTags.chips.addListener { change: ListChangeListener.Change<out String>? ->
|
|
||||||
mvc.updateTags()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -2,82 +2,105 @@ package org.hso.texturesyncclient.view.mainView
|
|||||||
|
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import javafx.scene.image.Image
|
import javafx.scene.image.Image
|
||||||
|
import org.hso.texturesyncclient.controller.RootController
|
||||||
import org.hso.texturesyncclient.model.GUIModel
|
import org.hso.texturesyncclient.model.GUIModel
|
||||||
import org.hso.texturesyncclient.model.TextureFormat
|
import org.hso.texturesyncclient.model.Texture
|
||||||
import tornadofx.Controller
|
import tornadofx.Controller
|
||||||
import javafx.stage.DirectoryChooser
|
|
||||||
import javax.swing.JColorChooser.showDialog
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
class MainViewController : Controller() {
|
class MainViewController : Controller() {
|
||||||
|
|
||||||
private val mv = find(MainView::class)
|
private val mv = find(MainView::class)
|
||||||
|
private val rootc = find(RootController::class)
|
||||||
|
|
||||||
// FolderView elements
|
// FolderView elements
|
||||||
private val folderView = mv.folderView.root
|
private val folderView = mv.folderView.root
|
||||||
|
|
||||||
// DetailView elements
|
// DetailView elements
|
||||||
private val preview = mv.detailView.preview
|
private val preview = mv.detailView.preview
|
||||||
private val metaLabel = mv.detailView.metaLabel
|
|
||||||
private val cvTags = mv.detailView.cvTags
|
private val cvTags = mv.detailView.cvTags
|
||||||
|
private var lockUpdate: Boolean = false //lock update func when the system changes the detailview chipview
|
||||||
|
|
||||||
// FolderView functions
|
// FolderView functions
|
||||||
fun addElement(element: GUIModel) {
|
fun addElement(element: GUIModel) {
|
||||||
folderView.children.add(element)
|
folderView.children.add(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAllElements(elementList: List<GUIModel>) {
|
|
||||||
folderView.children.addAll(elementList)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// DetailView functions
|
// DetailView functions
|
||||||
fun setPreview3DTexture(img: Image) {
|
fun setPreview3DTexture(img: Image) {
|
||||||
preview.setTexture(img)
|
preview.setTexture(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMeta(name: String, res: String, etc: String) {
|
fun setMeta(name: String, res: String, format: String, date: String) {
|
||||||
metaLabel.text = "Name: $name\nAuflösung: $res\nAnderes: $etc"
|
with(mv.detailView) {
|
||||||
|
nameInfo.text = name
|
||||||
|
formatInfo.text = format
|
||||||
|
resolutionInfo.text = res
|
||||||
|
dateInfo.text = date
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTags(chips: ObservableList<String>) {
|
fun setTags(chips: ObservableList<String>) {
|
||||||
|
lockUpdate = false //dont trigger update with onChange
|
||||||
cvTags.chips.clear()
|
cvTags.chips.clear()
|
||||||
cvTags.chips.addAll(chips)
|
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() {
|
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
|
||||||
* 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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DetailView actions
|
||||||
|
|
||||||
|
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!!) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -36,7 +36,6 @@ class Preview3D : View("Preview3D") {
|
|||||||
pointLightFront.rotate = 90.0
|
pointLightFront.rotate = 90.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override val root = stackpane {
|
override val root = stackpane {
|
||||||
|
|
||||||
add(dBox).apply {
|
add(dBox).apply {
|
||||||
@ -56,7 +55,7 @@ class Preview3D : View("Preview3D") {
|
|||||||
style {
|
style {
|
||||||
minWidth = 200.px
|
minWidth = 200.px
|
||||||
minHeight = 200.px
|
minHeight = 200.px
|
||||||
background = Background(BackgroundFill(Color.BLUEVIOLET, CornerRadii.EMPTY, Insets.EMPTY))
|
background = Background(BackgroundFill(Color.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import javafx.scene.layout.CornerRadii
|
|||||||
import javafx.scene.paint.Paint
|
import javafx.scene.paint.Paint
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class StartupView : View("StartupView") {
|
class StartupView : View("TextureSync") {
|
||||||
|
|
||||||
val labelStatus = Label("Verbindung zum Server einrichten")
|
val labelStatus = Label("Verbindung zum Server einrichten")
|
||||||
val spinnerStatus = JFXSpinner()
|
val spinnerStatus = JFXSpinner()
|
||||||
@ -22,6 +22,7 @@ class StartupView : View("StartupView") {
|
|||||||
|
|
||||||
private val svc: StartupViewController by inject()
|
private val svc: StartupViewController by inject()
|
||||||
|
|
||||||
|
|
||||||
override val root = borderpane {
|
override val root = borderpane {
|
||||||
minWidth = 1000.0
|
minWidth = 1000.0
|
||||||
minHeight = 500.0
|
minHeight = 500.0
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.hso.texturesyncclient.view.startupView
|
package org.hso.texturesyncclient.view.startupView
|
||||||
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
import org.hso.texturesyncclient.controller.RootController
|
import org.hso.texturesyncclient.controller.RootController
|
||||||
import tornadofx.Controller
|
import tornadofx.Controller
|
||||||
|
|
||||||
@ -10,24 +9,54 @@ class StartupViewController : Controller() {
|
|||||||
private val sv = find(StartupView::class)
|
private val sv = find(StartupView::class)
|
||||||
private val rootc = find(RootController::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) {
|
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.labelStatus.text = "Verbinden ..."
|
||||||
sv.tfServerIP.isEditable = false
|
sv.tfServerIP.isEditable = false
|
||||||
sv.btnConnect.isDisable = true
|
sv.btnConnect.isDisable = true
|
||||||
sv.spinnerStatus.isVisible = true
|
sv.spinnerStatus.isVisible = true
|
||||||
|
|
||||||
runAsync() {
|
|
||||||
rootc.initConnection(name)
|
|
||||||
} ui {
|
|
||||||
// reset for later use
|
|
||||||
sv.spinnerStatus.isVisible = false
|
|
||||||
sv.labelStatus.text = "Verbindung zum Server einrichten"
|
|
||||||
sv.tfServerIP.isEditable = true
|
|
||||||
sv.btnConnect.isDisable = false
|
|
||||||
sv.tfServerIP.clear()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
145
client/src/main/resources/css/Styles.css
Normal 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;
|
||||||
|
}
|
1
client/src/main/resources/icons/TextureSync_Icon.svg
Normal 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 |
BIN
client/src/main/resources/icons/TextureSync_Icon_256x256.jpeg
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
client/src/main/resources/icons/TextureSync_Icon_256x256.png
Normal file
After Width: | Height: | Size: 17 KiB |
15
doc/kurzanleitungen/client/inhalt.txt
Normal 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
|
||||||
|
|
7
doc/kurzanleitungen/server/inhalt.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
- herunterladen (Release Gitea)
|
||||||
|
- installiern (service daemon)
|
||||||
|
- starten
|
||||||
|
- loggen
|
||||||
|
- stoppen
|
||||||
|
- backups machen
|
||||||
|
- backups wieder einspielen
|
39525
doc/praesentation/TextureSync.fodp
Normal file
BIN
doc/praesentation/TextureSync.pdf
Normal file
4
doc/praesentation/vorgaben.txt
Normal 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)
|
@ -4,9 +4,9 @@
|
|||||||
<phases/>
|
<phases/>
|
||||||
<calendars>
|
<calendars>
|
||||||
<day-types>
|
<day-types>
|
||||||
<day-type id="0" name="Working" description="A default working day"/>
|
<day-type id="0" name="Arbeiten" description="Ein Vorgabe-Arbeitstag"/>
|
||||||
<day-type id="1" name="Nonworking" description="A default non working day"/>
|
<day-type id="1" name="Nicht Arbeiten" description="Ein Vorgabetag, an dem nicht gearbeitet wird"/>
|
||||||
<day-type id="2" name="Use base" description="Use day from base calendar"/>
|
<day-type id="2" name="Basis verwenden" description="Tag vom Basiskalender verwenden"/>
|
||||||
</day-types>
|
</day-types>
|
||||||
<calendar id="1" name="Vorgabe">
|
<calendar id="1" name="Vorgabe">
|
||||||
<default-week mon="1" tue="0" wed="0" thu="1" fri="1" sat="0" sun="1"/>
|
<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"/>
|
<interval start="1300" end="1700"/>
|
||||||
</overridden-day-type>
|
</overridden-day-type>
|
||||||
</overridden-day-types>
|
</overridden-day-types>
|
||||||
<days/>
|
<days>
|
||||||
|
<day date="20190613" type="day-type" id="0"/>
|
||||||
|
</days>
|
||||||
</calendar>
|
</calendar>
|
||||||
</calendars>
|
</calendars>
|
||||||
<tasks>
|
<tasks>
|
||||||
@ -92,88 +94,82 @@
|
|||||||
<predecessor id="1" predecessor-id="10" type="FS"/>
|
<predecessor id="1" predecessor-id="10" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="18" type="FS"/>
|
<predecessor id="1" predecessor-id="18" type="FS"/>
|
||||||
</predecessors>
|
</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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||||
</predecessors>
|
</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">
|
</task>
|
||||||
|
<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="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ü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="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="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"/>
|
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||||
</task>
|
</task>
|
||||||
</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="30" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||||
<predecessors>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||||
</predecessors>
|
</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">
|
<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">
|
||||||
<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ü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>
|
|
||||||
<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">
|
|
||||||
<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">
|
|
||||||
<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">
|
|
||||||
<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">
|
|
||||||
<constraint type="must-start-on" time="20190423T000000Z"/>
|
<constraint type="must-start-on" time="20190423T000000Z"/>
|
||||||
</task>
|
</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"/>
|
<constraint type="must-start-on" time="20190423T000000Z"/>
|
||||||
</task>
|
</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">
|
<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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
<predecessor id="1" predecessor-id="31" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</task>
|
||||||
<task id="35" name="Hauptlogik implementieren" note="Hauptsächlich Glue-Code " work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
<task id="34" name="Hauptlogik implementieren" note="Hauptsächlich Glue-Code " work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||||
<predecessors>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="33" type="FS"/>
|
|
||||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
<predecessor id="1" predecessor-id="32" type="FS"/>
|
||||||
|
<predecessor id="1" predecessor-id="31" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</task>
|
||||||
</task>
|
</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="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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="31" type="FS"/>
|
<predecessor id="1" predecessor-id="30" type="FS"/>
|
||||||
<predecessor id="1" predecessor-id="22" type="FS"/>
|
<predecessor id="1" predecessor-id="21" type="FS"/>
|
||||||
<predecessor id="1" predecessor-id="20" type="FS"/>
|
<predecessor id="1" predecessor-id="20" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</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="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="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="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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="36" type="FS"/>
|
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="38" type="FS"/>
|
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="39" type="FS"/>
|
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</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>
|
<predecessors>
|
||||||
<predecessor id="1" predecessor-id="40" type="FS"/>
|
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||||
</predecessors>
|
</predecessors>
|
||||||
</task>
|
</task>
|
||||||
</task>
|
</task>
|
||||||
@ -186,16 +182,17 @@
|
|||||||
<resource id="4" name="Lukas" short-name="L" type="1" units="0" email="" note="" std-rate="0"/>
|
<resource id="4" name="Lukas" short-name="L" type="1" units="0" email="" note="" std-rate="0"/>
|
||||||
</resources>
|
</resources>
|
||||||
<allocations>
|
<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="8" resource-id="1" units="100"/>
|
||||||
<allocation task-id="13" 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="11" resource-id="1" units="100"/>
|
||||||
<allocation task-id="5" resource-id="1" units="50"/>
|
<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="8" resource-id="2" units="10"/>
|
||||||
<allocation task-id="17" resource-id="2" units="100"/>
|
<allocation task-id="17" resource-id="2" units="100"/>
|
||||||
<allocation task-id="5" resource-id="2" units="50"/>
|
<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="15" resource-id="3" units="100"/>
|
||||||
<allocation task-id="12" resource-id="3" units="100"/>
|
<allocation task-id="12" resource-id="3" units="100"/>
|
||||||
<allocation task-id="10" resource-id="3" units="100"/>
|
<allocation task-id="10" resource-id="3" units="100"/>
|
||||||
|
38
orga/statusberichte/Statusbericht_KW23.md
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "texture-sync-server"
|
name = "texture-sync-server"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
authors = ["CodeSteak <codesteak@shellf.art>"]
|
authors = ["CodeSteak <codesteak@shellf.art>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ use std::path::*;
|
|||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
lovecraft::invoke();
|
lovecraft::invoke();
|
||||||
|
|
||||||
|
println!("\n\n\t=== TextureSync Server {} ===\t\n\n\n", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
let data_path = Path::new("./data");
|
let data_path = Path::new("./data");
|
||||||
println!("loading files from {:?}", data_path);
|
println!("loading files from {:?}", data_path);
|
||||||
let server_state = ServerState::new(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
|
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)?;
|
self::protocol::listen_forever(server_state, &network_conf)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
55
server/texture-sync-server/src/protocol/autoconnect/mod.rs
Normal 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(())
|
||||||
|
}
|
@ -16,8 +16,9 @@ where
|
|||||||
// set timeouts.
|
// set timeouts.
|
||||||
// We ignore errors here, so they will be caught in the clients thread.
|
// We ignore errors here, so they will be caught in the clients thread.
|
||||||
let _ = connection.as_mut().map(|stream| {
|
let _ = connection.as_mut().map(|stream| {
|
||||||
stream.set_read_timeout(Duration::from_secs(config.read_timeout_s).into())?;
|
let _ = 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_write_timeout(Duration::from_secs(config.write_timeout_s).into());
|
||||||
|
stream
|
||||||
});
|
});
|
||||||
|
|
||||||
let handler = handler.clone();
|
let handler = handler.clone();
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
mod implementation;
|
mod implementation;
|
||||||
|
|
||||||
pub use self::implementation::*;
|
pub use self::implementation::*;
|
||||||
|
|
||||||
|
mod autoconnect;
|
||||||
|
pub use self::autoconnect::start_autoconnect_server_async;
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::model::*;
|
use crate::model::*;
|
||||||
@ -53,6 +56,7 @@ pub struct ProtocolConfig {
|
|||||||
pub read_timeout_s: u64,
|
pub read_timeout_s: u64,
|
||||||
pub write_timeout_s: u64,
|
pub write_timeout_s: u64,
|
||||||
pub listen_addr: String,
|
pub listen_addr: String,
|
||||||
|
pub autoconnect_multicastv6_addr: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProtocolConfig {
|
impl ProtocolConfig {
|
||||||
@ -68,6 +72,7 @@ impl Default for ProtocolConfig {
|
|||||||
read_timeout_s: 60,
|
read_timeout_s: 60,
|
||||||
write_timeout_s: 75,
|
write_timeout_s: 75,
|
||||||
listen_addr: "::".to_owned(),
|
listen_addr: "::".to_owned(),
|
||||||
|
autoconnect_multicastv6_addr: "ff02::dd42:c0fe".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
testdata/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
bin
|
||||||
|
outputdir
|
22
testdata/create.sh
vendored
Executable 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
@ -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
@ -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
@ -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
@ -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
@ -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;
|
||||||
|
}
|
||||||
|
}
|