Compare commits
96 Commits
53dcebecde
...
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 |
@ -15,20 +15,8 @@ buildscript {
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
|
||||
}
|
||||
|
||||
group 'com.hso'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "no.tornado:tornadofx:$tornadofx_version"
|
||||
implementation "com.jfoenix:jfoenix:8.0.8"
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
id "com.github.johnrengelman.shadow" version "5.0.0"
|
||||
id "application"
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
@ -37,3 +25,19 @@ compileKotlin {
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
group 'org.hso'
|
||||
version '1.0'
|
||||
archivesBaseName = 'TextureSync'
|
||||
mainClassName = 'org.hso.texturesyncclient.app.Main'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "no.tornado:tornadofx:$tornadofx_version"
|
||||
implementation "com.jfoenix:jfoenix:8.0.9"
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
}
|
||||
|
@ -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,18 +1,36 @@
|
||||
package org.hso.texturesyncclient.app
|
||||
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import org.hso.texturesyncclient.view.importView.ImportView
|
||||
import org.hso.texturesyncclient.view.mainView.MainView
|
||||
import org.hso.texturesyncclient.view.mainView.MainViewController
|
||||
import javafx.scene.image.Image
|
||||
import javafx.stage.Stage
|
||||
import org.hso.texturesyncclient.controller.SettingsController
|
||||
import org.hso.texturesyncclient.view.startupView.StartupView
|
||||
import org.hso.texturesyncclient.view.startupView.StartupViewController
|
||||
import tornadofx.App
|
||||
|
||||
class Main : App(StartupView::class) {
|
||||
|
||||
val controller = RootController()
|
||||
private val svc: StartupViewController by inject()
|
||||
|
||||
init {
|
||||
// TODO get saved IP address, if found try to connect, else show StartupView
|
||||
override fun start(stage: Stage) {
|
||||
|
||||
// Calling super.start early prevents a weird layouting bug
|
||||
// were the button of the window is white.
|
||||
// This could(?) be caused by stage and StartupView having
|
||||
// a different min-size.
|
||||
super.start(stage)
|
||||
|
||||
stage.minWidth = 1050.00
|
||||
stage.minHeight = 700.00
|
||||
stage.width = 1050.00
|
||||
stage.height = 700.00
|
||||
stage.isResizable = true
|
||||
stage.icons.add(Image("icons/TextureSync_Icon_256x256.png"))
|
||||
stage.setOnCloseRequest { System.exit(0) }
|
||||
|
||||
stage.scene.stylesheets.add("/css/Styles.css")
|
||||
|
||||
SettingsController.init()
|
||||
svc.initConnection()
|
||||
}
|
||||
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
package org.hso.texturesyncclient.controller
|
||||
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.stage.DirectoryChooser
|
||||
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.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 tornadofx.observable
|
||||
import tornadofx.observableList
|
||||
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))
|
||||
con.ping()
|
||||
println("Connection successful")
|
||||
|
||||
// TODO store server ip for next start
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun search(tags: ObservableList<String>): ArrayList<GUIModel> {
|
||||
val previewList = arrayListOf<GUIModel>()
|
||||
|
||||
try {
|
||||
con.query(tags.toTypedArray()).forEach {
|
||||
previewList.add(GUIModel(it, con.getTexturePreview(it.textureHash)))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
}
|
||||
|
||||
println(previewList.size)
|
||||
|
||||
return previewList
|
||||
}
|
||||
|
||||
|
||||
fun switchToMainView(){
|
||||
find(StartupView::class).replaceWith(MainView::class, sizeToScene = true, centerOnScreen = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(System.getProperty("user.home"))
|
||||
|
||||
val dir = directoryChooser.showDialog(primaryStage)
|
||||
if (dir != null) {
|
||||
|
||||
// TODO copy data (bytesarray) with name and extension to dir
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun showDetail(data: Texture) {
|
||||
mvc.setPreview3DTexture(con.getTexturePreview(data.textureHash))
|
||||
mvc.setMeta(data.name, data.resolution.toString(), "")
|
||||
mvc.setTags(data.tags.toList().observable())
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
@Synchronized
|
||||
private fun getStreams(): Pair<DataInputStream, DataOutputStream> {
|
||||
private fun <R> withStreams(retry: Boolean = true, inner: (DataInputStream, DataOutputStream) -> R): R {
|
||||
val i: DataInputStream
|
||||
val o: DataOutputStream
|
||||
|
||||
@ -40,31 +40,40 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
o = output!!
|
||||
}
|
||||
|
||||
return Pair(i, o)
|
||||
return try {
|
||||
inner(i, o)
|
||||
} catch (e: IOException) {
|
||||
if (retry) {
|
||||
println("Got IOException; Retry ${e.message}")
|
||||
close()
|
||||
withStreams(false, inner)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun ping() {
|
||||
val io = getStreams()
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("ping", JsonObject())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
is JsonPackage -> return
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> return@withStreams
|
||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun query(query: Array<String>): Array<Texture> {
|
||||
val io = getStreams()
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("query", {
|
||||
@ -79,12 +88,15 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
try {
|
||||
return Gson().fromJson<Array<InternalTexture>>(pkg.content, Array<InternalTexture>::class.java)
|
||||
return@withStreams Gson().fromJson<Array<InternalTexture>>(
|
||||
pkg.content,
|
||||
Array<InternalTexture>::class.java
|
||||
)
|
||||
.map { tex ->
|
||||
tex.toTexture()
|
||||
}.toTypedArray()
|
||||
@ -97,11 +109,12 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun getTextureById(id: UUID): Texture? {
|
||||
val io = getStreams()
|
||||
return withStreams { i, o ->
|
||||
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture", {
|
||||
@ -110,11 +123,11 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
return if (pkg.content.isJsonNull) {
|
||||
return@withStreams if (pkg.content.isJsonNull) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
@ -132,11 +145,11 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun getTextureByName(name: String): Texture? {
|
||||
val io = getStreams()
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture", {
|
||||
@ -145,11 +158,11 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
return if (pkg.content.isJsonNull) {
|
||||
return@withStreams if (pkg.content.isJsonNull) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
@ -165,13 +178,12 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun getTextureFile(hash: Sha256): ByteArray {
|
||||
val io = getStreams()
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture_file", {
|
||||
@ -180,22 +192,20 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is BinaryPackage -> return pkg.content
|
||||
is BinaryPackage -> return@withStreams pkg.content
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
@Synchronized
|
||||
fun getTexturePreview(hash: Sha256): Image {
|
||||
val io = getStreams()
|
||||
|
||||
return withStreams { i, o ->
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture_preview", {
|
||||
val inner = JsonObject()
|
||||
@ -204,22 +214,23 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is BinaryPackage -> {
|
||||
return Image(ByteArrayInputStream(pkg.content))
|
||||
return@withStreams Image(ByteArrayInputStream(pkg.content))
|
||||
}
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
@Synchronized
|
||||
private fun replaceTexture(old: Texture?, new: Texture?, image: ByteArray?) {
|
||||
val io = getStreams()
|
||||
return withStreams { i, o ->
|
||||
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("replace_texture", {
|
||||
@ -237,19 +248,19 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(io.second)
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(io.first)) {
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
if (pkg.content == JsonPrimitive(true)) {
|
||||
// everthing is fine!
|
||||
return
|
||||
} else if (image != null) {
|
||||
when {
|
||||
pkg.content == JsonPrimitive(true) -> // everthing is fine!
|
||||
return@withStreams
|
||||
image != null -> {
|
||||
// should be { "get_texture_file": { texture_hash : <Hash> }}
|
||||
// we don't check, since there is no good way to handle it.
|
||||
|
||||
BinaryPackage(image).write(io.second)
|
||||
when (val ipkg = Package.read(io.first)) {
|
||||
BinaryPackage(image).write(o)
|
||||
when (val ipkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
if (ipkg.content != JsonPrimitive(true)) {
|
||||
// Protokoll Assertion failed
|
||||
@ -260,20 +271,22 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
is ErrorPackage -> throw ConnectionErrorException(ipkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
} else {
|
||||
ErrorPackage(404, "Texture not found!").write(io.second)
|
||||
}
|
||||
else -> {
|
||||
ErrorPackage(404, "Texture not found!").write(o)
|
||||
close() // gets re-opened on next request.
|
||||
throw IllegalArgumentException("Image Argument was needed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
@Synchronized
|
||||
fun uploadTexture(texture: Texture, image: ByteArray) {
|
||||
if (texture.textureHash != Sha256(image)) {
|
||||
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
||||
@ -283,7 +296,6 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
@Synchronized
|
||||
fun updateTexture(old: Texture, new: Texture, image: ByteArray) {
|
||||
if (new.textureHash != Sha256(image)) {
|
||||
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
||||
@ -293,14 +305,13 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
@Synchronized
|
||||
fun deleteTexture(texture: Texture) {
|
||||
replaceTexture(texture, null, null)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@Synchronized
|
||||
override fun close() {
|
||||
try {
|
||||
if (output != null) {
|
||||
output!!.close()
|
||||
output = null
|
||||
@ -313,6 +324,9 @@ class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
socket!!.close()
|
||||
socket = null
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -30,7 +30,7 @@ internal data class InternalTexture(
|
||||
resolution = arrayOf(tex.resolution.first, tex.resolution.second),
|
||||
added_on = arrayOf(
|
||||
tex.addedOn.get(Calendar.YEAR), //
|
||||
tex.addedOn.get(Calendar.MONTH), //
|
||||
tex.addedOn.get(Calendar.MONTH) + 1, //
|
||||
tex.addedOn.get(Calendar.DAY_OF_MONTH)
|
||||
),
|
||||
texture_hash = tex.textureHash.toString()
|
||||
@ -50,7 +50,7 @@ internal data class InternalTexture(
|
||||
else -> throw ConnectionInvalidJsonException()
|
||||
},
|
||||
resolution = Pair(resolution[0], resolution[1]),
|
||||
addedOn = GregorianCalendar(added_on[0], added_on[1], added_on[2]),
|
||||
addedOn = GregorianCalendar(added_on[0], added_on[1] - 1, added_on[2]),
|
||||
textureHash = Sha256(texture_hash)
|
||||
)
|
||||
} catch (e: Exception) { // i Know, but no time :[]
|
||||
|
@ -6,7 +6,7 @@ import java.lang.Exception
|
||||
|
||||
sealed class ConnectionException(override val message: String) : Exception(message)
|
||||
|
||||
class ConnectionErrorException(val errorCode: Int, val errorMessage: String) :
|
||||
class ConnectionErrorException(errorCode: Int, errorMessage: String) :
|
||||
ConnectionException("$errorCode $errorMessage") {
|
||||
internal constructor(err: ErrorPackage) : this(err.code, err.message)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ enum class TextureFormat {
|
||||
data class Texture(
|
||||
val id : UUID,
|
||||
val name : String,
|
||||
val tags : Array<String>,
|
||||
var tags : Array<String>,
|
||||
val format : TextureFormat,
|
||||
val resolution : Pair<Int, Int>,
|
||||
val addedOn : Calendar,
|
||||
|
@ -12,29 +12,50 @@ import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.paint.Paint
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import org.hso.texturesyncclient.view.mainView.MainViewController
|
||||
import tornadofx.*
|
||||
import tornadofx.addClass
|
||||
import tornadofx.find
|
||||
import tornadofx.paddingTop
|
||||
|
||||
class GUIModel constructor(data: Texture, img: Image) : VBox(){
|
||||
class GUIModel constructor(var data: Texture, img: Image) : VBox() {
|
||||
|
||||
private var image = ImageView()
|
||||
private var label = Label()
|
||||
private var contextMenu = ContextMenu()
|
||||
var exportItem = MenuItem("exportiern")
|
||||
private var exportItem = MenuItem("exportiern")
|
||||
private var deleteItem = MenuItem("löschen")
|
||||
|
||||
private val gmc = find(GUIModelController::class)
|
||||
|
||||
init {
|
||||
super.setPadding(Insets(5.0, 5.0, 5.0, 5.0))
|
||||
super.getChildren().addAll(image, label)
|
||||
super.setOnContextMenuRequested { p0 -> contextMenu.show(this@GUIModel, p0.screenX, p0.screenY) }
|
||||
super.setOnMouseClicked{ gmc.previewSelectedAction(data) }
|
||||
super.setOnMouseClicked {
|
||||
if (gmc.isLastSelectedInitialized()) {
|
||||
gmc.lastSelected.background = Background.EMPTY
|
||||
this.background = Background(BackgroundFill(Paint.valueOf("#2b7bbb"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
gmc.lastSelected = this
|
||||
} else {
|
||||
this.background = Background(BackgroundFill(Paint.valueOf("#2b7bbb"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
gmc.lastSelected = this
|
||||
}
|
||||
gmc.previewSelectedAction(data)
|
||||
gmc.setSelected(this)
|
||||
|
||||
}
|
||||
|
||||
label.addClass("metadata")
|
||||
label.paddingTop = 5.0
|
||||
label.prefWidth = 128.0
|
||||
label.alignment = Pos.CENTER
|
||||
label.text = data.name
|
||||
label.background = Background(BackgroundFill(Paint.valueOf("#FFFF2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
label.text = if (data.name.length > 15) {
|
||||
"${data.name.subSequence(0, 14)}.."
|
||||
} else {
|
||||
data.name
|
||||
}
|
||||
|
||||
label.background = Background(BackgroundFill(Paint.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
image.fitHeight = 128.0
|
||||
image.fitWidth = 128.0
|
||||
@ -44,7 +65,12 @@ class GUIModel constructor(data: Texture, img: Image) : VBox(){
|
||||
gmc.export(data)
|
||||
}
|
||||
|
||||
deleteItem.setOnAction {
|
||||
gmc.delete()
|
||||
}
|
||||
|
||||
contextMenu.items.add(exportItem)
|
||||
contextMenu.items.add(deleteItem)
|
||||
}
|
||||
|
||||
}
|
@ -7,11 +7,22 @@ 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.JFXChipView
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.geometry.Insets
|
||||
@ -11,24 +10,30 @@ import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import javafx.scene.layout.Priority
|
||||
import org.hso.texturesyncclient.view.mainView.Preview3D
|
||||
import tornadofx.*
|
||||
|
||||
class ImportView : View() {
|
||||
class ImportView : View("TextureSync") {
|
||||
|
||||
val tfFilePath = JFXTextField()
|
||||
val tfName = JFXTextField()
|
||||
val cvTags = JFXChipView<String>()
|
||||
val btnImport = JFXButton("Importieren")
|
||||
|
||||
val preview = Preview3D()
|
||||
|
||||
private val btnBack = JFXButton("Zurück")
|
||||
|
||||
private val ivc: ImportViewController by inject()
|
||||
|
||||
init {
|
||||
btnImport.isVisible = false
|
||||
preview.root.isVisible = false
|
||||
}
|
||||
|
||||
override val root = borderpane {
|
||||
minWidth = 1000.0
|
||||
minHeight = 500.0
|
||||
prefWidth = FX.primaryStage.width
|
||||
prefHeight = FX.primaryStage.height
|
||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
center = vbox(50) {
|
||||
@ -39,6 +44,8 @@ class ImportView : View() {
|
||||
style = "-fx-font: 20px Verdana; -fx-text-fill: #2b7bbb;"
|
||||
}
|
||||
|
||||
add(preview)
|
||||
|
||||
vbox(20) {
|
||||
hbox(10) {
|
||||
add(tfFilePath)
|
||||
@ -69,9 +76,8 @@ class ImportView : View() {
|
||||
vbox(5) {
|
||||
alignment = Pos.CENTER
|
||||
add(btnImport)
|
||||
add(btnBack)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,16 +89,17 @@ class ImportView : View() {
|
||||
tfName.style = "-fx-text-fill: #b15b2e;"
|
||||
tfName.promptText = "Name eingeben"
|
||||
|
||||
cvTags.style = "-fx-background-color: #3c3f41; -fx-text-inner-color: #b15b2e;"
|
||||
cvTags.style = "-fx-background-color: #53585b; -fx-text-inner-color: #b15b2e;"
|
||||
//TODO change color of Chip´s see: https://github.com/jfoenixadmin/JFoenix/blob/master/jfoenix/src/main/resources/com/jfoenix/assets/css/controls/jfx-chip-view.css#L52
|
||||
|
||||
btnImport.style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb;"
|
||||
btnImport.style = "-fx-button-type: RAISED; -fx-background-color: #b15b2e; -fx-text-fill: #3c3f41;"
|
||||
btnBack.style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb; -fx-padding: 10;"
|
||||
}
|
||||
|
||||
tfFilePath.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
||||
tfName.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
||||
|
||||
cvTags.chips.addListener { change: ListChangeListener.Change<out String>? ->
|
||||
cvTags.chips.onChange {
|
||||
ivc.validateImport()
|
||||
}
|
||||
|
||||
@ -100,6 +107,10 @@ class ImportView : View() {
|
||||
ivc.btnImportAction()
|
||||
}
|
||||
|
||||
btnBack.setOnAction {
|
||||
ivc.btnBackAction()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +1,68 @@
|
||||
package org.hso.texturesyncclient.view.importView
|
||||
|
||||
import javafx.stage.FileChooser.ExtensionFilter
|
||||
import javafx.scene.image.Image
|
||||
import javafx.stage.FileChooser
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import tornadofx.Controller
|
||||
import tornadofx.FileChooserMode
|
||||
import tornadofx.chooseFile
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
|
||||
class ImportViewController : Controller() {
|
||||
|
||||
private val iv = find(ImportView::class)
|
||||
private val rootc = find(RootController::class)
|
||||
|
||||
fun btnFileChooserAction() {
|
||||
val list = listOf("*.png", "*.PNG", "*.jpg", "*.JPG", "*.jpeg", "*.JPEG")
|
||||
val arrayFilter = arrayOf(ExtensionFilter("Texturen vom Bildformat: PNG oder JPG", list))
|
||||
val file = chooseFile("Textur auswählen", arrayFilter, FileChooserMode.Single, owner = null)
|
||||
private var lastImportDir: String = System.getProperty("user.home")
|
||||
|
||||
if (file.isNotEmpty()) {
|
||||
iv.tfFilePath.text = file[0].absolutePath
|
||||
iv.tfName.text = file[0].nameWithoutExtension
|
||||
fun btnFileChooserAction() {
|
||||
val fileChooser = FileChooser()
|
||||
val fileExtensions = FileChooser.ExtensionFilter(
|
||||
"Texturen vom Bildformat: PNG oder JPEG", "*.png", "*.PNG", "*.jpg", "*.JPG", "*.jpeg", "*.JPEG"
|
||||
)
|
||||
|
||||
fileChooser.extensionFilters.addAll(fileExtensions)
|
||||
fileChooser.initialDirectory = File(lastImportDir)
|
||||
fileChooser.title = "Textur auswählen"
|
||||
|
||||
val file = fileChooser.showOpenDialog(null)
|
||||
|
||||
if (file != null) {
|
||||
iv.tfFilePath.text = file.absolutePath
|
||||
iv.tfName.text = file.nameWithoutExtension
|
||||
lastImportDir = file.parent.toString() //store last user chosen dir
|
||||
|
||||
runAsync {
|
||||
try {
|
||||
val fileInput = FileInputStream(file.absolutePath)
|
||||
val img = Image(fileInput)
|
||||
iv.preview.setTexture(img)
|
||||
iv.preview.root.isVisible = true
|
||||
} catch (e: Exception) {
|
||||
// Got a catch'em all
|
||||
println(e.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun btnImportAction() {
|
||||
rootc.importTexture(iv.tfFilePath.text, iv.tfName.text, iv.cvTags.chips)
|
||||
RootController.switchImportToMain()
|
||||
iv.tfFilePath.clear()
|
||||
iv.tfName.clear()
|
||||
iv.cvTags.chips.clear()
|
||||
}
|
||||
|
||||
fun validateImport() {
|
||||
iv.btnImport.isVisible = iv.tfFilePath.text.isNotEmpty() && iv.tfName.text.isNotEmpty() && iv.cvTags.chips.isNotEmpty() && iv.cvTags.chips.stream().allMatch { x -> x.length < 32 }
|
||||
iv.btnImport.isVisible =
|
||||
iv.tfFilePath.text.isNotEmpty() && iv.tfName.text.isNotEmpty() && iv.cvTags.chips.isNotEmpty() && iv.cvTags.chips.stream().allMatch { x -> x.length < 32 }
|
||||
}
|
||||
|
||||
fun btnBackAction() {
|
||||
RootController.switchImportToMain()
|
||||
iv.tfFilePath.clear()
|
||||
iv.tfName.clear()
|
||||
iv.cvTags.chips.clear()
|
||||
}
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXChipView
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Orientation
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
@ -14,17 +15,37 @@ import tornadofx.*
|
||||
class DetailView : View() {
|
||||
|
||||
val preview = Preview3D()
|
||||
val metaLabel = Label("Auflösung: 8MP\nName: Texture.png\nAndere: was anderes")
|
||||
val cvTags = JFXChipView<String>()
|
||||
|
||||
init {
|
||||
// set a default texture
|
||||
preview.setTexture(Image("textures/sample_texture_1.jpg"))
|
||||
val nameInfo = JFXTextField().addClass("metadata")
|
||||
val resolutionInfo = label().addClass("metadata")
|
||||
val formatInfo = label().addClass("metadata")
|
||||
val dateInfo = label().addClass("metadata")
|
||||
|
||||
val btnSubmit = JFXButton("Ändern").addClass("btn-blue")
|
||||
|
||||
val metadataPanel = gridpane {
|
||||
row {
|
||||
label("Name ").addClass("metadata")
|
||||
add(nameInfo)
|
||||
}
|
||||
row {
|
||||
label("Auflösung ").addClass("metadata")
|
||||
add(resolutionInfo)
|
||||
}
|
||||
row {
|
||||
label("Format ").addClass("metadata")
|
||||
add(formatInfo)
|
||||
}
|
||||
row {
|
||||
label("Einfügedatum ").addClass("metadata")
|
||||
add(dateInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override val root = form {
|
||||
minWidth = 250.0
|
||||
background = Background(BackgroundFill(Paint.valueOf("#9f9f9f"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
background = Background(BackgroundFill(Paint.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
fieldset(labelPosition = Orientation.VERTICAL) {
|
||||
|
||||
@ -34,20 +55,26 @@ class DetailView: View() {
|
||||
}
|
||||
}
|
||||
|
||||
field("Meta") {
|
||||
add(metaLabel)
|
||||
field {
|
||||
add(metadataPanel)
|
||||
}
|
||||
|
||||
field {
|
||||
minHeight = 155.0
|
||||
add(cvTags)
|
||||
}
|
||||
|
||||
// TODO add "Import" Btn
|
||||
}
|
||||
|
||||
style {
|
||||
cvTags.style = "-fx-background-color: #3c3f41; -fx-text-inner-color: #b15b2e;"
|
||||
field {
|
||||
add(btnSubmit)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// set a default texture
|
||||
preview.setTexture(Image("icons/TextureSync_Icon_256x256.jpeg"))
|
||||
btnSubmit.useMaxWidth = true
|
||||
|
||||
}
|
||||
}
|
@ -10,16 +10,10 @@ import tornadofx.*
|
||||
class FolderView : View("FolderView"){
|
||||
|
||||
override val root = flowpane {
|
||||
hgap = 15.0
|
||||
vgap = 15.0
|
||||
paddingAll = 12.0
|
||||
prefWidth = 732.0
|
||||
prefHeight = 401.0
|
||||
background = Background(BackgroundFill(Paint.valueOf("#cfcfcf"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
//background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
style {
|
||||
hgap = 5.0
|
||||
vgap = 5.0
|
||||
paddingAll = 10.0
|
||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXChipView
|
||||
import com.jfoenix.controls.JFXSpinner
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.control.ScrollPane
|
||||
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
@ -11,49 +10,56 @@ import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.*
|
||||
|
||||
class MainView : View() {
|
||||
class MainView : View("TextureSync") {
|
||||
|
||||
val cvSearch = JFXChipView<String>()
|
||||
val spinnerSearch = JFXSpinner()
|
||||
private val btnImport = JFXButton("+")
|
||||
val folderView = find(FolderView::class)
|
||||
val detailView = find(DetailView::class)
|
||||
|
||||
private val mvc: MainViewController by inject()
|
||||
|
||||
override val root = borderpane {
|
||||
minWidth = 1000.0
|
||||
maxWidth = 1000.0
|
||||
minHeight = 500.0
|
||||
maxHeight = 500.0
|
||||
override val root = anchorpane {
|
||||
|
||||
left = vbox {
|
||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
prefWidth = FX.primaryStage.width
|
||||
prefHeight = FX.primaryStage.height
|
||||
|
||||
borderpane {
|
||||
right = detailView.root
|
||||
center = vbox {
|
||||
add(cvSearch)
|
||||
anchorpane {
|
||||
scrollpane {
|
||||
this.fitToParentSize()
|
||||
this.vbarPolicy = ScrollPane.ScrollBarPolicy.ALWAYS
|
||||
style = "-fx-background-color:transparent;"
|
||||
isFitToWidth = true
|
||||
isFitToHeight = true
|
||||
|
||||
add(folderView.root)
|
||||
}
|
||||
add(spinnerSearch)
|
||||
}
|
||||
|
||||
anchorpaneConstraints {
|
||||
topAnchor = 0
|
||||
bottomAnchor = 0
|
||||
rightAnchor = 0
|
||||
leftAnchor = 0
|
||||
}
|
||||
}
|
||||
|
||||
right = detailView.root
|
||||
add(btnImport)
|
||||
|
||||
style {
|
||||
spinnerSearch.isVisible = false
|
||||
spinnerSearch.anchorpaneConstraints {
|
||||
topAnchor = 150
|
||||
bottomAnchor = 150
|
||||
leftAnchor = 150
|
||||
rightAnchor = 150
|
||||
}
|
||||
cvSearch.promptText = "Suche"
|
||||
cvSearch.paddingAll = 5.0
|
||||
cvSearch.minHeight = 65.0
|
||||
cvSearch.style = "-fx-background-color: #53585b; -fx-text-inner-color: #b15b2e;"
|
||||
|
||||
cvSearch.paddingAll = 7.0
|
||||
cvSearch.minHeight = 70.0
|
||||
cvSearch.style = "-fx-background-color: #3c3f41; -fx-text-inner-color: #b15b2e;"
|
||||
btnImport.buttonType = JFXButton.ButtonType.RAISED
|
||||
btnImport.styleClass.add("jfx-floating-action-button")
|
||||
btnImport.anchorpaneConstraints {
|
||||
bottomAnchor = 5
|
||||
rightAnchor = 5
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
@ -61,10 +67,33 @@ class MainView : View() {
|
||||
mvc.cvSearchAction(cvSearch.chips)
|
||||
}
|
||||
|
||||
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,15 +2,10 @@ package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.scene.image.Image
|
||||
import org.hso.texturesyncclient.model.GUIModel
|
||||
import org.hso.texturesyncclient.model.TextureFormat
|
||||
import tornadofx.Controller
|
||||
import javafx.stage.DirectoryChooser
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import tornadofx.clear
|
||||
import javax.swing.JColorChooser.showDialog
|
||||
import java.io.File
|
||||
|
||||
import org.hso.texturesyncclient.model.GUIModel
|
||||
import org.hso.texturesyncclient.model.Texture
|
||||
import tornadofx.Controller
|
||||
|
||||
class MainViewController : Controller() {
|
||||
|
||||
@ -22,57 +17,90 @@ class MainViewController : Controller() {
|
||||
|
||||
// DetailView elements
|
||||
private val preview = mv.detailView.preview
|
||||
private val metaLabel = mv.detailView.metaLabel
|
||||
private val cvTags = mv.detailView.cvTags
|
||||
|
||||
private var lockUpdate: Boolean = false //lock update func when the system changes the detailview chipview
|
||||
|
||||
// FolderView functions
|
||||
fun addElement(element: GUIModel) {
|
||||
folderView.children.add(element)
|
||||
}
|
||||
|
||||
fun addAllElements(elementList: ArrayList<GUIModel>) {
|
||||
folderView.children.addAll(elementList)
|
||||
}
|
||||
|
||||
|
||||
// DetailView functions
|
||||
fun setPreview3DTexture(img: Image) {
|
||||
preview.setTexture(img)
|
||||
}
|
||||
|
||||
fun setMeta(name: String, res: String, etc: String) {
|
||||
metaLabel.text = "Name: $name\nAuflösung: $res\nAnderes: $etc"
|
||||
fun setMeta(name: String, res: String, format: String, date: String) {
|
||||
with(mv.detailView) {
|
||||
nameInfo.text = name
|
||||
formatInfo.text = format
|
||||
resolutionInfo.text = res
|
||||
dateInfo.text = date
|
||||
}
|
||||
}
|
||||
|
||||
fun setTags(chips: ObservableList<String>) {
|
||||
lockUpdate = false //dont trigger update with onChange
|
||||
cvTags.chips.clear()
|
||||
cvTags.chips.addAll(chips)
|
||||
lockUpdate = true //allow update with onChange
|
||||
}
|
||||
|
||||
// update the tags for the selected element
|
||||
fun updateTags() {
|
||||
println(cvTags.chips)
|
||||
fun getTags(): ObservableList<String> {
|
||||
return cvTags.chips
|
||||
}
|
||||
|
||||
fun updateTags() {
|
||||
if (lockUpdate) { //the chipView was changed by the user
|
||||
println("Tags changed")
|
||||
rootc.updateTexture(
|
||||
tags = cvTags.chips.toTypedArray(),
|
||||
name = mv.detailView.nameInfo.text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// DetailView actions
|
||||
|
||||
fun cvSearchAction(tags: ObservableList<String>) {
|
||||
var previewList = arrayListOf<GUIModel>()
|
||||
|
||||
// show spinner, block ui
|
||||
folderView.children.clear()
|
||||
mv.spinnerSearch.isVisible = true
|
||||
mv.cvSearch.isDisable = true
|
||||
setPreview3DTexture(Image("icons/TextureSync_Icon_256x256.jpeg")) // reset the 3DPreview to the logo
|
||||
RootController.selectedTexture = null
|
||||
|
||||
runAsync {
|
||||
previewList = rootc.search(tags)
|
||||
rootc.queryElements(tags)
|
||||
} ui {
|
||||
// when search finished
|
||||
addAllElements(previewList)
|
||||
mv.spinnerSearch.isVisible = false
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
override val root = stackpane {
|
||||
|
||||
add(dBox).apply {
|
||||
@ -56,7 +55,7 @@ class Preview3D : View("Preview3D") {
|
||||
style {
|
||||
minWidth = 200.px
|
||||
minHeight = 200.px
|
||||
background = Background(BackgroundFill(Color.BLUEVIOLET, CornerRadii.EMPTY, Insets.EMPTY))
|
||||
background = Background(BackgroundFill(Color.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.*
|
||||
|
||||
class StartupView : View("StartupView") {
|
||||
class StartupView : View("TextureSync") {
|
||||
|
||||
val labelStatus = Label("Verbindung zum Server einrichten")
|
||||
val spinnerStatus = JFXSpinner()
|
||||
@ -22,6 +22,7 @@ class StartupView : View("StartupView") {
|
||||
|
||||
private val svc: StartupViewController by inject()
|
||||
|
||||
|
||||
override val root = borderpane {
|
||||
minWidth = 1000.0
|
||||
minHeight = 500.0
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.hso.texturesyncclient.view.startupView
|
||||
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import org.hso.texturesyncclient.view.mainView.MainView
|
||||
import tornadofx.Controller
|
||||
|
||||
|
||||
@ -10,25 +9,54 @@ class StartupViewController : Controller() {
|
||||
private val sv = find(StartupView::class)
|
||||
private val rootc = find(RootController::class)
|
||||
|
||||
fun btnConnectAction(name: String) {
|
||||
sv.labelStatus.text = "Verbinden ..."
|
||||
sv.tfServerIP.isEditable = false
|
||||
sv.btnConnect.isDisable = true
|
||||
sv.spinnerStatus.isVisible = true
|
||||
fun initConnection() {
|
||||
println("init StartupViewController")
|
||||
startConnectionUI()
|
||||
runAsync {
|
||||
rootc.initConnection(" ")
|
||||
} ui {
|
||||
// reset for later use
|
||||
endConnectionUI()
|
||||
}
|
||||
}
|
||||
|
||||
fun setServerAddress(address: String) {
|
||||
//sv.tfServerIP.text = address
|
||||
sv.tfServerIP.isFocusTraversable = false
|
||||
}
|
||||
|
||||
fun btnConnectAction(name: String) {
|
||||
startConnectionUI()
|
||||
runAsync {
|
||||
rootc.initConnection(name)
|
||||
} ui {
|
||||
// reset for later use
|
||||
endConnectionUI()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show spinner and block textfied + button and set label
|
||||
*/
|
||||
private fun startConnectionUI() {
|
||||
sv.labelStatus.text = "Verbinden ..."
|
||||
sv.tfServerIP.isEditable = false
|
||||
sv.btnConnect.isDisable = true
|
||||
sv.spinnerStatus.isVisible = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
rootc.switchToMainView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
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/>
|
||||
<calendars>
|
||||
<day-types>
|
||||
<day-type id="0" name="Working" description="A default working day"/>
|
||||
<day-type id="1" name="Nonworking" description="A default non working day"/>
|
||||
<day-type id="2" name="Use base" description="Use day from base calendar"/>
|
||||
<day-type id="0" name="Arbeiten" description="Ein Vorgabe-Arbeitstag"/>
|
||||
<day-type id="1" name="Nicht Arbeiten" description="Ein Vorgabetag, an dem nicht gearbeitet wird"/>
|
||||
<day-type id="2" name="Basis verwenden" description="Tag vom Basiskalender verwenden"/>
|
||||
</day-types>
|
||||
<calendar id="1" name="Vorgabe">
|
||||
<default-week mon="1" tue="0" wed="0" thu="1" fri="1" sat="0" sun="1"/>
|
||||
@ -16,7 +16,9 @@
|
||||
<interval start="1300" end="1700"/>
|
||||
</overridden-day-type>
|
||||
</overridden-day-types>
|
||||
<days/>
|
||||
<days>
|
||||
<day date="20190613" type="day-type" id="0"/>
|
||||
</days>
|
||||
</calendar>
|
||||
</calendars>
|
||||
<tasks>
|
||||
@ -92,88 +94,82 @@
|
||||
<predecessor id="1" predecessor-id="10" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="19" name="Realisation" note="" work="1065600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="19" name="Realisation" note="" work="1641600" start="20190416T133640Z" end="20190612T170000Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="18" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="20" name="Einrichtung von Tests" note="" work="28800" start="20190423T000000Z" end="20190423T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="20" name="Einrichtung von Tests" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="21" name="Client Tests" note="" work="28800" start="20190423T000000Z" end="20190423T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
</task>
|
||||
<task id="22" name="Client" note="" work="547200" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="21" name="Client" note="" work="1123200" start="20190416T133640Z" end="20190612T170000Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="23" name="UI" note="" work="288000" start="20190423T170000Z" end="20190430T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="21" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="24" name="Previews" note="" work="86400" start="20190423T170000Z" end="20190430T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="25" name="Filter UI" note="" work="86400" start="20190423T170000Z" end="20190430T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="26" name="3D Visualisierung" note="" work="57600" start="20190423T170000Z" end="20190427T170000Z" work-start="20190424T080000Z" percent-complete="50" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="27" name="Dateien hinzufügen" note="" work="28800" start="20190423T170000Z" end="20190424T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="28" name="Dateien lokal abspeichern" note="" work="28800" start="20190423T170000Z" end="20190424T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="22" name="UI" note="" work="288000" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="23" name="Previews" note="" work="86400" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="24" name="Filter UI" note="" work="86400" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="25" name="3D Visualisierung" note="" work="57600" start="20190416T133640Z" end="20190420T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="26" name="Dateien hinzufügen" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="27" name="Dateien lokal abspeichern" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
</task>
|
||||
<task id="29" name="Grund Architektur" note="" work="86400" start="20190423T000000Z" end="20190427T170000Z" work-start="20190423T080000Z" percent-complete="70" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="28" name="Grund Architektur" note="" work="662400" start="20190423T000000Z" end="20190612T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
<task id="30" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="29" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
</task>
|
||||
<task id="31" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="30" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="32" name="Dateien speichern/verwalten" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="31" name="Dateien speichern/verwalten" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="must-start-on" time="20190423T000000Z"/>
|
||||
</task>
|
||||
<task id="33" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="32" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="must-start-on" time="20190423T000000Z"/>
|
||||
</task>
|
||||
<task id="34" name="Suche implementieren" note="" work="86400" start="20190504T170000Z" end="20190511T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="35" name="Hauptlogik implementieren" note="Hauptsächlich Glue-Code " work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="33" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
</task>
|
||||
</task>
|
||||
<task id="36" name="End of Implementation" note="" work="0" start="20190511T170000Z" end="20190511T170000Z" work-start="20190511T170000Z" percent-complete="0" priority="0" type="milestone" scheduling="fixed-work">
|
||||
<task id="33" name="Suche implementieren" note="" work="86400" start="20190504T170000Z" end="20190511T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="31" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="22" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="34" name="Hauptlogik implementieren" note="Hauptsächlich Glue-Code " work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="31" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
</task>
|
||||
</task>
|
||||
<task id="35" name="End of Implementation" note="" work="0" start="20190612T170000Z" end="20190612T170000Z" work-start="20190612T170000Z" percent-complete="0" priority="0" type="milestone" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="30" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="21" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="20" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="37" name="Abnahme" note="" work="374400" start="20190511T170000Z" end="20190611T170000Z" work-start="20190514T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="38" name="Modultests [M]" note="" work="115200" start="20190511T170000Z" end="20190521T170000Z" work-start="20190514T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="36" name="Abnahme" note="" work="115200" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="37" name="Modultests [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="36" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="39" name="Integrationstests [M]" note="" work="115200" start="20190521T170000Z" end="20190529T170000Z" work-start="20190522T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="38" name="Integrationstests [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="38" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="40" name="Systemtests [M]" note="" work="115200" start="20190529T170000Z" end="20190608T170000Z" work-start="20190601T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="39" name="Systemtests [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="39" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="41" name="Akzeptanztest [M]" note="" work="28800" start="20190608T170000Z" end="20190611T170000Z" work-start="20190611T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="40" name="Akzeptanztest [M]" note="" work="28800" start="20190612T170000Z" end="20190613T170000Z" work-start="20190613T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="40" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="35" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
</task>
|
||||
@ -186,16 +182,17 @@
|
||||
<resource id="4" name="Lukas" short-name="L" type="1" units="0" email="" note="" std-rate="0"/>
|
||||
</resources>
|
||||
<allocations>
|
||||
<allocation task-id="33" resource-id="1" units="100"/>
|
||||
<allocation task-id="29" resource-id="1" units="100"/>
|
||||
<allocation task-id="32" resource-id="1" units="100"/>
|
||||
<allocation task-id="8" resource-id="1" units="100"/>
|
||||
<allocation task-id="13" resource-id="1" units="100"/>
|
||||
<allocation task-id="11" resource-id="1" units="100"/>
|
||||
<allocation task-id="5" resource-id="1" units="50"/>
|
||||
<allocation task-id="29" resource-id="2" units="50"/>
|
||||
<allocation task-id="28" resource-id="2" units="50"/>
|
||||
<allocation task-id="8" resource-id="2" units="10"/>
|
||||
<allocation task-id="17" resource-id="2" units="100"/>
|
||||
<allocation task-id="5" resource-id="2" units="50"/>
|
||||
<allocation task-id="29" resource-id="3" units="50"/>
|
||||
<allocation task-id="28" resource-id="3" units="50"/>
|
||||
<allocation task-id="15" resource-id="3" units="100"/>
|
||||
<allocation task-id="12" resource-id="3" units="100"/>
|
||||
<allocation task-id="10" resource-id="3" units="100"/>
|
||||
|
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]
|
||||
name = "texture-sync-server"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
authors = ["CodeSteak <codesteak@shellf.art>"]
|
||||
edition = "2018"
|
||||
|
||||
|
@ -24,6 +24,8 @@ use std::path::*;
|
||||
fn main() -> std::io::Result<()> {
|
||||
lovecraft::invoke();
|
||||
|
||||
println!("\n\n\t=== TextureSync Server {} ===\t\n\n\n", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let data_path = Path::new("./data");
|
||||
println!("loading files from {:?}", data_path);
|
||||
let server_state = ServerState::new(data_path)?;
|
||||
@ -35,6 +37,10 @@ fn main() -> std::io::Result<()> {
|
||||
network_conf.listen_addr, network_conf.port
|
||||
);
|
||||
|
||||
match self::protocol::start_autoconnect_server_async(&network_conf) {
|
||||
Ok(_) => println!("Started Autoconnect Server"),
|
||||
Err(e) => println!("Starting Autoconnect Server failed: {:?}", e),
|
||||
}
|
||||
self::protocol::listen_forever(server_state, &network_conf)?;
|
||||
|
||||
Ok(())
|
||||
|
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.
|
||||
// We ignore errors here, so they will be caught in the clients thread.
|
||||
let _ = connection.as_mut().map(|stream| {
|
||||
stream.set_read_timeout(Duration::from_secs(config.read_timeout_s).into())?;
|
||||
stream.set_write_timeout(Duration::from_secs(config.write_timeout_s).into())?;
|
||||
let _ = stream.set_read_timeout(Duration::from_secs(config.read_timeout_s).into());
|
||||
let _ = stream.set_write_timeout(Duration::from_secs(config.write_timeout_s).into());
|
||||
stream
|
||||
});
|
||||
|
||||
let handler = handler.clone();
|
||||
|
@ -1,6 +1,9 @@
|
||||
mod implementation;
|
||||
|
||||
pub use self::implementation::*;
|
||||
|
||||
mod autoconnect;
|
||||
pub use self::autoconnect::start_autoconnect_server_async;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::model::*;
|
||||
@ -53,6 +56,7 @@ pub struct ProtocolConfig {
|
||||
pub read_timeout_s: u64,
|
||||
pub write_timeout_s: u64,
|
||||
pub listen_addr: String,
|
||||
pub autoconnect_multicastv6_addr: String,
|
||||
}
|
||||
|
||||
impl ProtocolConfig {
|
||||
@ -68,6 +72,7 @@ impl Default for ProtocolConfig {
|
||||
read_timeout_s: 60,
|
||||
write_timeout_s: 75,
|
||||
listen_addr: "::".to_owned(),
|
||||
autoconnect_multicastv6_addr: "ff02::dd42:c0fe".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
testdata/.gitignore
vendored
Normal file
@ -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;
|
||||
}
|
||||
}
|