Compare commits
186 Commits
e77bd4012b
...
1.0.0
Author | SHA1 | Date | |
---|---|---|---|
8c6a43fc22
|
|||
7f4182bfad
|
|||
5fff4023f9
|
|||
dfad679e6e | |||
391dfe4282
|
|||
225316abb9 | |||
f5dbcc0134 | |||
cac4807357 | |||
33fa741a2a | |||
37406acd98 | |||
56c62422cc | |||
bc43b33536 | |||
1ac13f80d5 | |||
8dfbbae559 | |||
cc03b32ade | |||
a2f5b65e30 | |||
f094a68b04 | |||
b5545e1bae | |||
9a9a1e0056 | |||
0f23749001 | |||
c1dbd8ffb7 | |||
ebc6449e48 | |||
bef56f978c | |||
50ea54c8d6 | |||
24b9755089 | |||
6fbfa050b3 | |||
9a3524143a | |||
10076fcef2 | |||
6219ad93d0 | |||
8109782e4e | |||
c2d01760c7 | |||
2cac52b48d | |||
35f8145112 | |||
e26d54e96f | |||
7e3ce9ce56
|
|||
9975364621 | |||
491578806e | |||
b1052fa894
|
|||
36c4805061
|
|||
ee7fbae29c | |||
07f2682a2d
|
|||
efc28455c1 | |||
5d223ba72b
|
|||
15cbbb1bac | |||
08840c4c2b | |||
6b6ef67122
|
|||
4a213afdf5 | |||
36a8bccfe3 | |||
4f35293151 | |||
29354199cc | |||
f4db466b37 | |||
19eb6660e1 | |||
4e8b4c364b | |||
f22a9eb260
|
|||
9ae50df1a4
|
|||
ce11d2e263
|
|||
3f18760a74 | |||
89ef736954 | |||
3e1c46db81
|
|||
be11f35159 | |||
39c358c00f
|
|||
61980905e3 | |||
fc5db5d051 | |||
4c2e1c2917 | |||
a82cef599c
|
|||
86008828f5 | |||
c1ec000b98 | |||
ed558106e5 | |||
da153091bd
|
|||
9200e9277a
|
|||
40bfc73ba8 | |||
b1984ac8dd | |||
1c1f12ecaf
|
|||
d5b7fd0f94
|
|||
2c9a23ec8a
|
|||
f45d807536 | |||
ed344cbd49
|
|||
8e7ba1cb46 | |||
b2a84db5a7 | |||
71f50b832e | |||
0f8d2ba256 | |||
b0a1844b16 | |||
594f083a8d
|
|||
90b21ca3d1 | |||
db8178f4c4 | |||
4cd4481182 | |||
3c2984a404 | |||
c55182d6cc | |||
e540e58161 | |||
727558d867 | |||
adc8725a36 | |||
99ef2a8edc | |||
de47e99852
|
|||
d7a67c497e
|
|||
8d387250e7 | |||
e32766e581 | |||
53dcebecde
|
|||
c80233155f
|
|||
857f35e77e | |||
e78e7e3a59 | |||
cf37e579ad | |||
d5853d7006 | |||
00fc294396 | |||
88b04f7aea | |||
83e4f10aaa | |||
ca2ad079d4 | |||
ae92167206 | |||
a3a86cf913 | |||
47cf586116 | |||
b709b231c4 | |||
b2ba9a9ba4 | |||
189b1955d7 | |||
3e5d2da366 | |||
fd749069a1 | |||
cf4e5b3e92
|
|||
0c93c0fedb
|
|||
c26a1b80f1 | |||
c36d81aa92
|
|||
c975730a58 | |||
37c4983d8e | |||
67401817a0 | |||
972d199154 | |||
d7e2977f2a | |||
d1f2f42511 | |||
73fd94e314 | |||
35aa0409da
|
|||
7f8277415e | |||
badd50de38 | |||
872fc6c289 | |||
d7dd858d42 | |||
40cda05ad4 | |||
2e344da2f4 | |||
1b41132c18 | |||
d907ff138a | |||
2c064fe15c | |||
ec9c6888fc | |||
0b7cf9c7cd | |||
c2bd135dd9 | |||
6f9931a04f | |||
a322068fdb
|
|||
60e9f7f4ad | |||
adfafe1fc6 | |||
7ced269906 | |||
000c28ff4f | |||
a2089dbd92 | |||
92b053769a | |||
89b2b480ce | |||
c7eff9080b
|
|||
c0438a6e34
|
|||
805cfed250
|
|||
956e885aa2
|
|||
77cecebe52 | |||
195b352cd2 | |||
6ec289cdb9 | |||
cde0238d62 | |||
3198fbc514 | |||
10b775cdc9 | |||
0090628452 | |||
d9d4f16c01 | |||
5db62d4d08 | |||
55cff24e9b | |||
2784e8d2ff | |||
b53b3af8c0 | |||
66fc54cb3e | |||
cd5e69145f | |||
cb2b8339a6 | |||
9f5961c3a8 | |||
cfc2ff5886 | |||
ee432a0421 | |||
232fd6c1de | |||
6dd72e3e69 | |||
573cbda27f | |||
28085f9b87 | |||
5369304516 | |||
88ba7a6dd3 | |||
156912d4f6 | |||
89fa985005 | |||
073eb9011c | |||
24f7aca1bb
|
|||
c571a755ff
|
|||
56ececee83 | |||
acf27a2d7b | |||
96c2864a07 | |||
f5dcd52454 | |||
04fdb838b8 | |||
b2a1323e8e |
3
.gitignore
vendored
@ -54,3 +54,6 @@ CMakeLists.txt.user*
|
||||
#IDEA
|
||||
**/.idea
|
||||
**/*.iml
|
||||
|
||||
# TextureSync Server Data Dir
|
||||
/server/texture-sync-server/data
|
||||
|
@ -15,10 +15,21 @@ buildscript {
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
|
||||
id "com.github.johnrengelman.shadow" version "5.0.0"
|
||||
id "application"
|
||||
}
|
||||
|
||||
group 'com.hso'
|
||||
version '1.0-SNAPSHOT'
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
group 'org.hso'
|
||||
version '1.0'
|
||||
archivesBaseName = 'TextureSync'
|
||||
mainClassName = 'org.hso.texturesyncclient.app.Main'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@ -27,12 +38,6 @@ repositories {
|
||||
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.jfoenix:jfoenix:8.0.9"
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -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,15 +1,36 @@
|
||||
package org.hso.texturesyncclient.app
|
||||
|
||||
import org.hso.texturesyncclient.view.importView.ImportView
|
||||
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){
|
||||
class Main : App(StartupView::class) {
|
||||
|
||||
private val svc: StartupViewController by inject()
|
||||
|
||||
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,7 +0,0 @@
|
||||
package org.hso.texturesyncclient.controller
|
||||
|
||||
class Controller {
|
||||
|
||||
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,332 @@
|
||||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import com.google.gson.*
|
||||
import javafx.scene.image.Image
|
||||
import org.hso.texturesyncclient.model.Sha256
|
||||
import org.hso.texturesyncclient.model.Texture
|
||||
import java.io.*
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
import java.util.*
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class Connection(val address: InetAddress, val port: Int = 10796) : Closeable {
|
||||
|
||||
var socket: Socket? = null
|
||||
var output: DataOutputStream? = null
|
||||
var input: DataInputStream? = null
|
||||
|
||||
@Throws(IOException::class)
|
||||
@Synchronized
|
||||
private fun <R> withStreams(retry: Boolean = true, inner: (DataInputStream, DataOutputStream) -> R): R {
|
||||
val i: DataInputStream
|
||||
val o: DataOutputStream
|
||||
|
||||
if (socket == null || !socket!!.isConnected) {
|
||||
val sock = Socket()
|
||||
sock.soTimeout = 10_000 /*ms*/
|
||||
sock.keepAlive = true
|
||||
sock.connect(InetSocketAddress(address, port), 1_000 /*ms*/)
|
||||
|
||||
i = DataInputStream(BufferedInputStream(sock.getInputStream()))
|
||||
o = DataOutputStream(BufferedOutputStream(sock.getOutputStream()))
|
||||
|
||||
input = i
|
||||
output = o
|
||||
socket = sock
|
||||
} else {
|
||||
i = input!!
|
||||
o = output!!
|
||||
}
|
||||
|
||||
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)
|
||||
fun ping() {
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("ping", JsonObject())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
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)
|
||||
fun query(query: Array<String>): Array<Texture> {
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("query", {
|
||||
val inner = JsonObject()
|
||||
inner.add("query", {
|
||||
val array = JsonArray()
|
||||
for (queryString in query) {
|
||||
array.add(queryString)
|
||||
}
|
||||
array
|
||||
}())
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
try {
|
||||
return@withStreams Gson().fromJson<Array<InternalTexture>>(
|
||||
pkg.content,
|
||||
Array<InternalTexture>::class.java
|
||||
)
|
||||
.map { tex ->
|
||||
tex.toTexture()
|
||||
}.toTypedArray()
|
||||
} catch (e: JsonSyntaxException) {
|
||||
throw ConnectionInvalidJsonException()
|
||||
}
|
||||
}
|
||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
fun getTextureById(id: UUID): Texture? {
|
||||
return withStreams { i, o ->
|
||||
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture", {
|
||||
val inner = JsonObject()
|
||||
inner.addProperty("id", id.toString())
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
return@withStreams if (pkg.content.isJsonNull) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
Gson()
|
||||
.fromJson<InternalTexture>(pkg.content, InternalTexture::class.java)
|
||||
.toTexture()
|
||||
} catch (e: JsonSyntaxException) {
|
||||
throw ConnectionInvalidJsonException()
|
||||
}
|
||||
}
|
||||
}
|
||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
fun getTextureByName(name: String): Texture? {
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture", {
|
||||
val inner = JsonObject()
|
||||
inner.addProperty("name", name)
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
return@withStreams if (pkg.content.isJsonNull) {
|
||||
null
|
||||
} else {
|
||||
try {
|
||||
Gson()
|
||||
.fromJson<InternalTexture>(pkg.content, InternalTexture::class.java)
|
||||
.toTexture()
|
||||
} catch (e: JsonSyntaxException) {
|
||||
throw ConnectionInvalidJsonException()
|
||||
}
|
||||
}
|
||||
}
|
||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
fun getTextureFile(hash: Sha256): ByteArray {
|
||||
return withStreams { i, o ->
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture_file", {
|
||||
val inner = JsonObject()
|
||||
inner.addProperty("texture_hash", hash.toString())
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is BinaryPackage -> return@withStreams pkg.content
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class)
|
||||
fun getTexturePreview(hash: Sha256): Image {
|
||||
return withStreams { i, o ->
|
||||
val obj = JsonObject()
|
||||
obj.add("get_texture_preview", {
|
||||
val inner = JsonObject()
|
||||
inner.addProperty("texture_hash", hash.toString())
|
||||
inner.addProperty("desired_format", "jpeg")
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is BinaryPackage -> {
|
||||
return@withStreams Image(ByteArrayInputStream(pkg.content))
|
||||
}
|
||||
is ErrorPackage -> throw ConnectionErrorException(pkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
private fun replaceTexture(old: Texture?, new: Texture?, image: ByteArray?) {
|
||||
return withStreams { i, o ->
|
||||
|
||||
|
||||
val obj = JsonObject()
|
||||
obj.add("replace_texture", {
|
||||
val inner = JsonObject()
|
||||
if (old != null) {
|
||||
inner.add("old", Gson().toJsonTree(InternalTexture(old), InternalTexture::class.java))
|
||||
} else {
|
||||
inner.add("old", null)
|
||||
}
|
||||
if (new != null) {
|
||||
inner.add("new", Gson().toJsonTree(InternalTexture(new), InternalTexture::class.java))
|
||||
} else {
|
||||
inner.add("new", null)
|
||||
}
|
||||
inner
|
||||
}())
|
||||
|
||||
JsonPackage(obj).write(o)
|
||||
|
||||
when (val pkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
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(o)
|
||||
when (val ipkg = Package.read(i)) {
|
||||
is JsonPackage -> {
|
||||
if (ipkg.content != JsonPrimitive(true)) {
|
||||
// Protokoll Assertion failed
|
||||
throw ConnectionUnexpectedPacketException()
|
||||
}
|
||||
}
|
||||
is BinaryPackage -> throw ConnectionUnexpectedPacketException()
|
||||
is ErrorPackage -> throw ConnectionErrorException(ipkg)
|
||||
else -> throw RuntimeException("Unreachable")
|
||||
}
|
||||
}
|
||||
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)
|
||||
fun uploadTexture(texture: Texture, image: ByteArray) {
|
||||
if (texture.textureHash != Sha256(image)) {
|
||||
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
||||
}
|
||||
|
||||
replaceTexture(null, texture, image)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
fun updateTexture(old: Texture, new: Texture, image: ByteArray) {
|
||||
if (new.textureHash != Sha256(image)) {
|
||||
throw IllegalArgumentException("Sha256 of Image does not Match with Texture.")
|
||||
}
|
||||
|
||||
replaceTexture(old, new, image)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ConnectionException::class, IllegalArgumentException::class)
|
||||
fun deleteTexture(texture: Texture) {
|
||||
replaceTexture(texture, null, null)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
try {
|
||||
if (output != null) {
|
||||
output!!.close()
|
||||
output = null
|
||||
}
|
||||
if (input != null) {
|
||||
input!!.close()
|
||||
input = null
|
||||
}
|
||||
if (socket != null) {
|
||||
socket!!.close()
|
||||
socket = null
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
// These types will be converted to the model.DataModel
|
||||
|
||||
import org.hso.texturesyncclient.model.Sha256
|
||||
import org.hso.texturesyncclient.model.Texture
|
||||
import org.hso.texturesyncclient.model.TextureFormat
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
internal data class InternalTexture(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val tags: Array<String>,
|
||||
val format: String,
|
||||
val resolution: Array<Int>,
|
||||
val added_on: Array<Int>,
|
||||
val texture_hash: String
|
||||
) {
|
||||
|
||||
constructor(tex: Texture) : this(
|
||||
id = tex.id.toString(),
|
||||
name = tex.name,
|
||||
tags = tex.tags.clone(),
|
||||
format = when (tex.format) {
|
||||
TextureFormat.PNG -> "png"
|
||||
TextureFormat.JPEG -> "jpeg"
|
||||
},
|
||||
resolution = arrayOf(tex.resolution.first, tex.resolution.second),
|
||||
added_on = arrayOf(
|
||||
tex.addedOn.get(Calendar.YEAR), //
|
||||
tex.addedOn.get(Calendar.MONTH) + 1, //
|
||||
tex.addedOn.get(Calendar.DAY_OF_MONTH)
|
||||
),
|
||||
texture_hash = tex.textureHash.toString()
|
||||
)
|
||||
|
||||
@Throws(ConnectionException::class)
|
||||
fun toTexture(): Texture {
|
||||
try {
|
||||
return Texture(
|
||||
id = UUID.fromString(id),
|
||||
name = name,
|
||||
tags = tags.clone(),
|
||||
format = when (format.toLowerCase()) {
|
||||
"jpeg" -> TextureFormat.JPEG
|
||||
"jpg" -> TextureFormat.JPEG
|
||||
"png" -> TextureFormat.PNG
|
||||
else -> throw ConnectionInvalidJsonException()
|
||||
},
|
||||
resolution = Pair(resolution[0], resolution[1]),
|
||||
addedOn = GregorianCalendar(added_on[0], added_on[1] - 1, added_on[2]),
|
||||
textureHash = Sha256(texture_hash)
|
||||
)
|
||||
} catch (e: Exception) { // i Know, but no time :[]
|
||||
throw ConnectionInvalidJsonException()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonParser
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
internal abstract class Package {
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun write(out: DataOutputStream)
|
||||
|
||||
companion object {
|
||||
|
||||
const val TYPE_ERROR: Byte = 0
|
||||
protected const val TYPE_ERROR_MAX_PAYLOAD: Int = 1024
|
||||
|
||||
const val TYPE_JSON: Byte = 1
|
||||
protected const val TYPE_JSON_MAX_PAYLOAD: Int = 16 * 1024 * 1024
|
||||
|
||||
const val TYPE_BINARY: Byte = 2
|
||||
protected const val TYPE_BINARY_MAX_PAYLOAD: Int = 512 * 1024 * 1024
|
||||
|
||||
@Throws(PacketException::class, IOException::class)
|
||||
fun read(input: DataInputStream): Package {
|
||||
// Type Byte
|
||||
val type = input.readByte()
|
||||
|
||||
// 3 Reserved Bytes
|
||||
input.readByte()
|
||||
input.readByte()
|
||||
input.readByte()
|
||||
|
||||
// 4 Len Bytes
|
||||
val length = input.readInt()
|
||||
|
||||
when (type) {
|
||||
TYPE_ERROR -> if (length > TYPE_ERROR_MAX_PAYLOAD) {
|
||||
throw PacketTooLongException()
|
||||
}
|
||||
|
||||
TYPE_JSON -> if (length > TYPE_JSON_MAX_PAYLOAD) {
|
||||
throw PacketTooLongException()
|
||||
}
|
||||
|
||||
TYPE_BINARY -> if (length > TYPE_BINARY_MAX_PAYLOAD) {
|
||||
throw PacketTooLongException()
|
||||
}
|
||||
|
||||
else -> throw PacketInvalidType()
|
||||
}
|
||||
|
||||
val payload = ByteArray(length)
|
||||
input.readFully(payload)
|
||||
|
||||
when (type) {
|
||||
TYPE_ERROR -> {
|
||||
val msg = String(payload)
|
||||
val results = msg.split(' ', ignoreCase = false, limit = 2)
|
||||
|
||||
return if (results.size == 2) {
|
||||
val code = results[0].toIntOrNull()
|
||||
|
||||
if (code == null) {
|
||||
ErrorPackage(0, msg)
|
||||
} else {
|
||||
ErrorPackage(code, results[1])
|
||||
}
|
||||
} else {
|
||||
ErrorPackage(0, msg)
|
||||
}
|
||||
}
|
||||
|
||||
TYPE_JSON -> {
|
||||
try {
|
||||
val obj = JsonParser().parse(String(payload))
|
||||
|
||||
return JsonPackage(obj)
|
||||
} catch (e: JsonParseException) {
|
||||
throw PacketInvalidData()
|
||||
}
|
||||
}
|
||||
|
||||
TYPE_BINARY -> {
|
||||
return BinaryPackage(payload)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Unreachable
|
||||
throw PacketInvalidType()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal data class JsonPackage(val content: JsonElement) : Package() {
|
||||
override fun write(out: DataOutputStream) {
|
||||
val payload = content.toString().toByteArray()
|
||||
// Tag Byte
|
||||
out.writeByte(TYPE_JSON.toInt())
|
||||
|
||||
// 3 Reserved Bytes
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
|
||||
// Length of Payload
|
||||
out.writeInt(payload.size)
|
||||
|
||||
// Payload
|
||||
out.write(payload)
|
||||
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
internal data class BinaryPackage(val content: ByteArray) : Package() {
|
||||
override fun write(out: DataOutputStream) {
|
||||
// Tag Byte
|
||||
out.writeByte(TYPE_BINARY.toInt())
|
||||
|
||||
// 3 Reserved Bytes
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
|
||||
// Length of Payload
|
||||
out.writeInt(content.size)
|
||||
|
||||
// Payload
|
||||
out.write(content)
|
||||
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ErrorPackage(val code: Int, val message: String) : Package() {
|
||||
override fun write(out: DataOutputStream) {
|
||||
val payload = "$code $message".toByteArray()
|
||||
// Tag Byte
|
||||
out.writeByte(TYPE_ERROR.toInt())
|
||||
|
||||
// 3 Reserved Bytes
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
out.writeByte(0x42)
|
||||
|
||||
// Length of Payload
|
||||
out.writeInt(payload.size)
|
||||
|
||||
// Payload
|
||||
out.write(payload)
|
||||
|
||||
out.flush()
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
||||
|
||||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import java.lang.Exception
|
||||
|
||||
sealed class ConnectionException(override val message: String) : Exception(message)
|
||||
|
||||
class ConnectionErrorException(errorCode: Int, errorMessage: String) :
|
||||
ConnectionException("$errorCode $errorMessage") {
|
||||
internal constructor(err: ErrorPackage) : this(err.code, err.message)
|
||||
}
|
||||
|
||||
class ConnectionUnexpectedPacketException : ConnectionException("Got Unexpected Type of Packet")
|
||||
class ConnectionInvalidJsonException : ConnectionException("The Format of the Json Received is Unexpected.")
|
||||
|
||||
|
||||
sealed class PacketException(msg: String) : ConnectionException(msg)
|
||||
class PacketTooLongException : PacketException("The Package is too long.")
|
||||
class PacketInvalidType : PacketException("The Package has an Invalid Type.")
|
||||
class PacketInvalidData : PacketException("The Package has an Invalid Data. (e.g. Invalid Json.)")
|
@ -0,0 +1,22 @@
|
||||
package org.hso.texturesyncclient.controller.net
|
||||
|
||||
import java.net.*
|
||||
|
||||
fun main() {
|
||||
// Just some test code.
|
||||
|
||||
val con = Connection(InetAddress.getByName("::1"))
|
||||
|
||||
con.ping()
|
||||
|
||||
|
||||
println("Query:")
|
||||
for (tex in con.query(
|
||||
arrayOf("Red", "Food")
|
||||
)) {
|
||||
println(tex.toString())
|
||||
}
|
||||
println()
|
||||
|
||||
con.close()
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package org.hso.texturesyncclient.model
|
||||
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.*
|
||||
import java.security.MessageDigest
|
||||
|
||||
|
||||
enum class TextureFormat {
|
||||
PNG, JPEG,
|
||||
}
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
data class Texture(
|
||||
val id : UUID,
|
||||
val name : String,
|
||||
var tags : Array<String>,
|
||||
val format : TextureFormat,
|
||||
val resolution : Pair<Int, Int>,
|
||||
val addedOn : Calendar,
|
||||
val textureHash : Sha256
|
||||
)
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
class Sha256 {
|
||||
|
||||
private val hashBytes : ByteArray
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
constructor(hex : String) {
|
||||
|
||||
if(hex.length != 64) {
|
||||
throw IllegalArgumentException("Sha256 has wrong length.")
|
||||
}
|
||||
|
||||
try {
|
||||
hashBytes = ByteArray(hex.length / 2) { i ->
|
||||
hex.substring(i * 2, i * 2 + 2).toInt(16).toByte()
|
||||
}
|
||||
}catch(n : NumberFormatException) {
|
||||
throw IllegalArgumentException("Hash does not only Contain '0-9a-zA-Z'.")
|
||||
}
|
||||
}
|
||||
|
||||
constructor(data : ByteArray ) {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
hashBytes = digest.digest(data)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val s = StringBuilder(64)
|
||||
for (byte in hashBytes) {
|
||||
s.append(String.format("%02X", byte))
|
||||
}
|
||||
return s.toString()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Sha256
|
||||
|
||||
if (!hashBytes.contentEquals(other.hashBytes)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return hashBytes.contentHashCode()
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.hso.texturesyncclient.model
|
||||
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.ContextMenu
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.MenuItem
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.addClass
|
||||
import tornadofx.find
|
||||
import tornadofx.paddingTop
|
||||
|
||||
class GUIModel constructor(var data: Texture, img: Image) : VBox() {
|
||||
|
||||
private var image = ImageView()
|
||||
private var label = Label()
|
||||
private var contextMenu = ContextMenu()
|
||||
private var exportItem = MenuItem("exportiern")
|
||||
private var deleteItem = MenuItem("löschen")
|
||||
|
||||
private val gmc = find(GUIModelController::class)
|
||||
|
||||
init {
|
||||
super.setPadding(Insets(5.0, 5.0, 5.0, 5.0))
|
||||
super.getChildren().addAll(image, label)
|
||||
super.setOnContextMenuRequested { p0 -> contextMenu.show(this@GUIModel, p0.screenX, p0.screenY) }
|
||||
super.setOnMouseClicked {
|
||||
if (gmc.isLastSelectedInitialized()) {
|
||||
gmc.lastSelected.background = Background.EMPTY
|
||||
this.background = Background(BackgroundFill(Paint.valueOf("#2b7bbb"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
gmc.lastSelected = this
|
||||
} else {
|
||||
this.background = Background(BackgroundFill(Paint.valueOf("#2b7bbb"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
gmc.lastSelected = this
|
||||
}
|
||||
gmc.previewSelectedAction(data)
|
||||
gmc.setSelected(this)
|
||||
|
||||
}
|
||||
|
||||
label.addClass("metadata")
|
||||
label.paddingTop = 5.0
|
||||
label.prefWidth = 128.0
|
||||
label.alignment = Pos.CENTER
|
||||
|
||||
label.text = if (data.name.length > 15) {
|
||||
"${data.name.subSequence(0, 14)}.."
|
||||
} else {
|
||||
data.name
|
||||
}
|
||||
|
||||
label.background = Background(BackgroundFill(Paint.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
image.fitHeight = 128.0
|
||||
image.fitWidth = 128.0
|
||||
image.image = img
|
||||
|
||||
exportItem.setOnAction {
|
||||
gmc.export(data)
|
||||
}
|
||||
|
||||
deleteItem.setOnAction {
|
||||
gmc.delete()
|
||||
}
|
||||
|
||||
contextMenu.items.add(exportItem)
|
||||
contextMenu.items.add(deleteItem)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.hso.texturesyncclient.model
|
||||
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import tornadofx.Controller
|
||||
|
||||
class GUIModelController : Controller() {
|
||||
|
||||
private val rootc = find(RootController::class)
|
||||
|
||||
lateinit var lastSelected: GUIModel
|
||||
fun isLastSelectedInitialized() = ::lastSelected.isInitialized
|
||||
|
||||
fun export(data: Texture) {
|
||||
rootc.exportTexture(data)
|
||||
}
|
||||
|
||||
fun delete() {
|
||||
rootc.deleteTexture()
|
||||
}
|
||||
|
||||
fun previewSelectedAction(data: Texture) {
|
||||
rootc.showDetail(data)
|
||||
}
|
||||
|
||||
fun setSelected(model: GUIModel) {
|
||||
rootc.setSelectedTexture(model)
|
||||
}
|
||||
}
|
@ -4,71 +4,111 @@ import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXChipView
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.text.Font
|
||||
import javafx.scene.text.FontWeight
|
||||
import org.hso.texturesyncclient.view.mainView.Preview3D
|
||||
import tornadofx.*
|
||||
|
||||
class ImportView : View() {
|
||||
class ImportView : View("TextureSync") {
|
||||
|
||||
val labelHeading = Label("Textur hinzufügen")
|
||||
val tfFilePath = JFXTextField()
|
||||
val btnFileChooser = JFXButton("Datei öffnen")
|
||||
val labelName = Label("Name")
|
||||
val tfName = JFXTextField()
|
||||
val labelTags = Label("Tags")
|
||||
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) {
|
||||
maxWidth = 350.0
|
||||
alignment = Pos.CENTER
|
||||
|
||||
add(labelHeading)
|
||||
label("Textur hinzufügen") {
|
||||
style = "-fx-font: 20px Verdana; -fx-text-fill: #2b7bbb;"
|
||||
}
|
||||
|
||||
add(preview)
|
||||
|
||||
vbox(20) {
|
||||
hbox(10) {
|
||||
add(tfFilePath)
|
||||
add(btnFileChooser)
|
||||
button("Datei öffnen") {
|
||||
style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb;"
|
||||
|
||||
setOnAction {
|
||||
ivc.btnFileChooserAction()
|
||||
ivc.validateImport()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vbox(5) {
|
||||
add(labelName)
|
||||
label("Name") {
|
||||
style = "-fx-font: 14px Verdana; -fx-text-fill: #2b7bbb;"
|
||||
}
|
||||
add(tfName)
|
||||
}
|
||||
|
||||
vbox(5) {
|
||||
add(labelTags)
|
||||
label("Tags") {
|
||||
style = "-fx-font: 14px Verdana; -fx-text-fill: #2b7bbb;"
|
||||
}
|
||||
add(cvTags)
|
||||
}
|
||||
|
||||
vbox(5) {
|
||||
alignment = Pos.CENTER
|
||||
add(btnImport)
|
||||
add(btnBack)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
style {
|
||||
labelHeading.font = Font.font("Verdana", FontWeight.MEDIUM, 40.0)
|
||||
|
||||
tfFilePath.promptText = "Pfad ..."
|
||||
tfFilePath.style = "-fx-text-fill: #b15b2e;"
|
||||
tfFilePath.promptText = "Pfad zur Datei"
|
||||
tfFilePath.hgrow = Priority.ALWAYS
|
||||
btnFileChooser.style = "-fx-button-type: RAISED; -fx-background-color: #FAAFFF; -fx-text-fill: BLACK;"
|
||||
|
||||
labelName.style = "-fx-font: 14px Verdana; -fx-text-fill: BLACK;"
|
||||
tfName.promptText = "Name ..."
|
||||
tfName.style = "-fx-text-fill: #b15b2e;"
|
||||
tfName.promptText = "Name eingeben"
|
||||
|
||||
labelTags.style = "-fx-font: 14px Verdana; -fx-text-fill: BLACK;"
|
||||
cvTags.style = "-fx-background-color: #FFFFFF;"
|
||||
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: #b15b2e; -fx-text-fill: #3c3f41;"
|
||||
btnBack.style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb; -fx-padding: 10;"
|
||||
}
|
||||
|
||||
btnFileChooser.setOnAction {
|
||||
ivc.btnFileChooserAction()
|
||||
tfFilePath.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
||||
tfName.textProperty().addListener{ _, _, _ -> ivc.validateImport() }
|
||||
|
||||
cvTags.chips.onChange {
|
||||
ivc.validateImport()
|
||||
}
|
||||
|
||||
btnImport.setOnAction {
|
||||
ivc.btnImportAction()
|
||||
}
|
||||
|
||||
btnBack.setOnAction {
|
||||
ivc.btnBackAction()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,68 @@
|
||||
package org.hso.texturesyncclient.view.importView
|
||||
|
||||
import javafx.scene.image.Image
|
||||
import javafx.stage.FileChooser
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import tornadofx.Controller
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
|
||||
class ImportViewController : Controller() {
|
||||
|
||||
private val iv = find(ImportView::class)
|
||||
private val rootc = find(RootController::class)
|
||||
|
||||
private var lastImportDir: String = System.getProperty("user.home")
|
||||
|
||||
fun btnFileChooserAction() {
|
||||
println("btn click")
|
||||
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 }
|
||||
}
|
||||
|
||||
fun btnBackAction() {
|
||||
RootController.switchImportToMain()
|
||||
iv.tfFilePath.clear()
|
||||
iv.tfName.clear()
|
||||
iv.cvTags.chips.clear()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,80 @@
|
||||
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.image.Image
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.*
|
||||
|
||||
class DetailView : View() {
|
||||
|
||||
val preview = Preview3D()
|
||||
val cvTags = JFXChipView<String>()
|
||||
|
||||
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("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
fieldset(labelPosition = Orientation.VERTICAL) {
|
||||
|
||||
field {
|
||||
vbox(7) {
|
||||
add(preview)
|
||||
}
|
||||
}
|
||||
|
||||
field {
|
||||
add(metadataPanel)
|
||||
}
|
||||
|
||||
field {
|
||||
minHeight = 155.0
|
||||
add(cvTags)
|
||||
}
|
||||
|
||||
field {
|
||||
add(btnSubmit)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// set a default texture
|
||||
preview.setTexture(Image("icons/TextureSync_Icon_256x256.jpeg"))
|
||||
btnSubmit.useMaxWidth = true
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.*
|
||||
|
||||
class FolderView : View("FolderView"){
|
||||
|
||||
override val root = flowpane {
|
||||
hgap = 5.0
|
||||
vgap = 5.0
|
||||
paddingAll = 10.0
|
||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
}
|
||||
|
||||
}
|
@ -1,15 +1,99 @@
|
||||
package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import javafx.scene.Parent
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXChipView
|
||||
import javafx.geometry.Insets
|
||||
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.*
|
||||
|
||||
class MainView : View() {
|
||||
class MainView : View("TextureSync") {
|
||||
|
||||
val cvSearch = JFXChipView<String>()
|
||||
private val btnImport = JFXButton("+")
|
||||
val folderView = find(FolderView::class)
|
||||
val detailView = find(DetailView::class)
|
||||
|
||||
override val root: Parent
|
||||
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
|
||||
private val mvc: MainViewController by inject()
|
||||
|
||||
override val root = anchorpane {
|
||||
|
||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
prefWidth = FX.primaryStage.width
|
||||
prefHeight = FX.primaryStage.height
|
||||
|
||||
borderpane {
|
||||
right = detailView.root
|
||||
center = vbox {
|
||||
add(cvSearch)
|
||||
scrollpane {
|
||||
style = "-fx-background-color:transparent;"
|
||||
isFitToWidth = true
|
||||
isFitToHeight = true
|
||||
|
||||
add(folderView.root)
|
||||
}
|
||||
}
|
||||
|
||||
anchorpaneConstraints {
|
||||
topAnchor = 0
|
||||
bottomAnchor = 0
|
||||
rightAnchor = 0
|
||||
leftAnchor = 0
|
||||
}
|
||||
}
|
||||
|
||||
add(btnImport)
|
||||
|
||||
style {
|
||||
cvSearch.promptText = "Suche"
|
||||
cvSearch.paddingAll = 5.0
|
||||
cvSearch.minHeight = 65.0
|
||||
cvSearch.style = "-fx-background-color: #53585b; -fx-text-inner-color: #b15b2e;"
|
||||
|
||||
btnImport.buttonType = JFXButton.ButtonType.RAISED
|
||||
btnImport.styleClass.add("jfx-floating-action-button")
|
||||
btnImport.anchorpaneConstraints {
|
||||
bottomAnchor = 5
|
||||
rightAnchor = 5
|
||||
}
|
||||
}
|
||||
|
||||
// actions
|
||||
cvSearch.chips.onChange {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,106 @@
|
||||
package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.scene.image.Image
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import org.hso.texturesyncclient.model.GUIModel
|
||||
import org.hso.texturesyncclient.model.Texture
|
||||
import tornadofx.Controller
|
||||
|
||||
class MainViewController : Controller() {
|
||||
|
||||
private val mv = find(MainView::class)
|
||||
private val rootc = find(RootController::class)
|
||||
|
||||
// FolderView elements
|
||||
private val folderView = mv.folderView.root
|
||||
|
||||
// DetailView elements
|
||||
private val preview = mv.detailView.preview
|
||||
private val 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)
|
||||
}
|
||||
|
||||
// DetailView functions
|
||||
fun setPreview3DTexture(img: Image) {
|
||||
preview.setTexture(img)
|
||||
}
|
||||
|
||||
fun setMeta(name: String, res: String, format: String, date: String) {
|
||||
with(mv.detailView) {
|
||||
nameInfo.text = name
|
||||
formatInfo.text = format
|
||||
resolutionInfo.text = res
|
||||
dateInfo.text = date
|
||||
}
|
||||
}
|
||||
|
||||
fun setTags(chips: ObservableList<String>) {
|
||||
lockUpdate = false //dont trigger update with onChange
|
||||
cvTags.chips.clear()
|
||||
cvTags.chips.addAll(chips)
|
||||
lockUpdate = true //allow update with onChange
|
||||
}
|
||||
|
||||
fun getTags(): ObservableList<String> {
|
||||
return cvTags.chips
|
||||
}
|
||||
|
||||
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>) {
|
||||
// show spinner, block ui
|
||||
folderView.children.clear()
|
||||
mv.cvSearch.isDisable = true
|
||||
setPreview3DTexture(Image("icons/TextureSync_Icon_256x256.jpeg")) // reset the 3DPreview to the logo
|
||||
RootController.selectedTexture = null
|
||||
|
||||
runAsync {
|
||||
rootc.queryElements(tags)
|
||||
} ui {
|
||||
// when search finished
|
||||
mv.cvSearch.isDisable = false
|
||||
}
|
||||
}
|
||||
|
||||
fun btnImportAction() {
|
||||
RootController.switchMainToImport()
|
||||
}
|
||||
|
||||
fun removeTextureFromView(data: Texture) {
|
||||
// stream all children nodes, filter them as GUIModel with data.id == data.id, for any found object if it's still present remove it from the folderView
|
||||
folderView.children.stream().filter { x -> (x as GUIModel).data.id == data.id }.findAny()
|
||||
.ifPresent { x -> folderView.children.remove(x) }
|
||||
}
|
||||
|
||||
fun setVisibleMetaTags(bool: Boolean) {
|
||||
if (bool) {
|
||||
mv.detailView.metadataPanel.isVisible = true
|
||||
mv.detailView.btnSubmit.isVisible = false
|
||||
cvTags.isVisible = true
|
||||
} else {
|
||||
mv.detailView.metadataPanel.isVisible = false
|
||||
mv.detailView.btnSubmit.isVisible = false
|
||||
cvTags.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
fun scExport() {
|
||||
RootController.selectedTexture?.let { rootc.exportTexture(RootController.selectedTexture!!) }
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package org.hso.texturesyncclient.view.mainView
|
||||
|
||||
import javafx.animation.Interpolator
|
||||
import javafx.animation.RotateTransition
|
||||
import javafx.geometry.Insets
|
||||
import javafx.scene.AmbientLight
|
||||
import javafx.scene.PointLight
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.PhongMaterial
|
||||
import javafx.scene.shape.Box
|
||||
import javafx.scene.transform.Rotate
|
||||
import tornadofx.*
|
||||
|
||||
class Preview3D : View("Preview3D") {
|
||||
|
||||
private val boxSize = 100.0
|
||||
private val dBox = Box(boxSize, boxSize, boxSize)
|
||||
private val rxBox = Rotate(45.0, 0.0, 0.0, 0.0, Rotate.X_AXIS)
|
||||
private val ryBox = Rotate(45.0, 0.0, 0.0, 0.0, Rotate.Y_AXIS)
|
||||
|
||||
private val pointLightFront = PointLight(Color.WHITE)
|
||||
private val ambient = AmbientLight(Color.ANTIQUEWHITE)
|
||||
|
||||
init {
|
||||
// add rotation to the box
|
||||
dBox.transforms.addAll(rxBox, ryBox)
|
||||
|
||||
// light stuff
|
||||
pointLightFront.translateX = boxSize
|
||||
pointLightFront.translateY = boxSize
|
||||
pointLightFront.translateZ = (-2 * boxSize)
|
||||
pointLightFront.rotate = 90.0
|
||||
}
|
||||
|
||||
override val root = stackpane {
|
||||
|
||||
add(dBox).apply {
|
||||
// rotate the box from 0° to 360° for infinity around the y axis
|
||||
dBox.rotationAxis = Rotate.Y_AXIS
|
||||
timeline {
|
||||
keyframe(18.seconds) {
|
||||
keyvalue(dBox.rotateProperty(), 360.0, interpolator = Interpolator.LINEAR)
|
||||
}
|
||||
cycleCount = RotateTransition.INDEFINITE
|
||||
}
|
||||
}
|
||||
|
||||
add(pointLightFront)
|
||||
add(ambient)
|
||||
|
||||
style {
|
||||
minWidth = 200.px
|
||||
minHeight = 200.px
|
||||
background = Background(BackgroundFill(Color.valueOf("#3a3a3a"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* set the texture of the bock
|
||||
* @param img the texture to set
|
||||
*/
|
||||
fun setTexture(img: Image) {
|
||||
val textureMaterial = PhongMaterial()
|
||||
textureMaterial.diffuseMap = img
|
||||
dBox.material = textureMaterial
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package org.hso.texturesyncclient.view.startupView
|
||||
|
||||
import com.jfoenix.animation.alert.CenterTransition
|
||||
import com.jfoenix.controls.JFXButton
|
||||
import com.jfoenix.controls.JFXSpinner
|
||||
import com.jfoenix.controls.JFXTextField
|
||||
@ -11,25 +10,26 @@ import javafx.scene.input.KeyCode
|
||||
import javafx.scene.layout.Background
|
||||
import javafx.scene.layout.BackgroundFill
|
||||
import javafx.scene.layout.CornerRadii
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.Paint
|
||||
import tornadofx.*
|
||||
|
||||
class StartupView : View() {
|
||||
class StartupView : View("TextureSync") {
|
||||
|
||||
val labelStatus = Label("Verbinden")
|
||||
val labelStatus = Label("Verbindung zum Server einrichten")
|
||||
val spinnerStatus = JFXSpinner()
|
||||
val labelServerIP = Label("Server-IP")
|
||||
val tfServerIP = JFXTextField()
|
||||
val btnConnect = JFXButton("Manuell Verbinden")
|
||||
|
||||
private val svc: StartupViewController by inject()
|
||||
|
||||
|
||||
override val root = borderpane {
|
||||
minWidth = 1000.0
|
||||
minHeight = 500.0
|
||||
background = Background(BackgroundFill(Paint.valueOf("#2b2b2b"), CornerRadii.EMPTY, Insets.EMPTY))
|
||||
|
||||
center = vbox(50) {
|
||||
maxWidth = 150.0
|
||||
maxWidth = 350.0
|
||||
alignment = Pos.CENTER
|
||||
|
||||
add(labelStatus)
|
||||
@ -38,36 +38,39 @@ class StartupView : View() {
|
||||
vbox(10) {
|
||||
alignment = Pos.CENTER
|
||||
|
||||
add(labelServerIP)
|
||||
label("Server-Adresse") {
|
||||
style = "-fx-font: 15px Verdana; -fx-text-fill: #2b7bbb;"
|
||||
}
|
||||
add(tfServerIP)
|
||||
add(btnConnect)
|
||||
|
||||
// tfServerIP.style { alignment=Center} TODO
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
style {
|
||||
labelStatus.background = Background(BackgroundFill(Color.YELLOWGREEN, CornerRadii.EMPTY, Insets.EMPTY))
|
||||
spinnerStatus.isVisible = false
|
||||
|
||||
labelStatus.style = "-fx-font: 20px Verdana; -fx-text-fill: #2b7bbb;"
|
||||
|
||||
btnConnect.style = "-fx-button-type: RAISED; -fx-background-color: #3c3f41; -fx-text-fill: #2b7bbb;"
|
||||
|
||||
//tfServerIP.style = "-fx-text-fill: #b15b2e;"
|
||||
tfServerIP.style {
|
||||
textFill = Paint.valueOf("#b15b2e")
|
||||
alignment = Pos.BASELINE_CENTER
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
btnConnect.setOnAction {
|
||||
spinnerStatus.isVisible = true
|
||||
svc.btnConnectAction(tfServerIP.text)
|
||||
spinnerStatus.isVisible = false
|
||||
|
||||
}
|
||||
|
||||
tfServerIP.setOnKeyPressed {
|
||||
if (it.code == KeyCode.ENTER) {
|
||||
spinnerStatus.isVisible = true
|
||||
svc.btnConnectAction(tfServerIP.text)
|
||||
spinnerStatus.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,35 +1,62 @@
|
||||
package org.hso.texturesyncclient.view.startupView
|
||||
|
||||
import org.hso.texturesyncclient.controller.RootController
|
||||
import tornadofx.Controller
|
||||
import com.sun.org.apache.xalan.internal.xsltc.compiler.sym.PATTERN
|
||||
|
||||
|
||||
|
||||
class StartupViewController : Controller() {
|
||||
|
||||
fun btnConnectAction(txt:String){
|
||||
println("Connect BTN: $txt")
|
||||
private val sv = find(StartupView::class)
|
||||
private val rootc = find(RootController::class)
|
||||
|
||||
if (validateIPv4(txt)){
|
||||
println("valid ipv4")
|
||||
//TODO connect to server ...
|
||||
}else{
|
||||
println("invalid ipv4")
|
||||
//TODO show error
|
||||
fun initConnection() {
|
||||
println("init StartupViewController")
|
||||
startConnectionUI()
|
||||
runAsync {
|
||||
rootc.initConnection(" ")
|
||||
} ui {
|
||||
// reset for later use
|
||||
endConnectionUI()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun labelStatusSetText (txt:String){
|
||||
val startupView = find(StartupView::class)
|
||||
startupView.labelStatus.text = txt
|
||||
fun setServerAddress(address: String) {
|
||||
//sv.tfServerIP.text = address
|
||||
sv.tfServerIP.isFocusTraversable = false
|
||||
}
|
||||
|
||||
fun validateIPv4(ip: String): Boolean {
|
||||
val regex = Regex(pattern = "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])\$")
|
||||
return regex.containsMatchIn(input = ip) // matched: true
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
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 |
BIN
client/src/main/resources/textures/sample_texture_1.jpg
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
client/src/main/resources/textures/sample_texture_2.jpg
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
client/src/main/resources/textures/sample_texture_3.PNG
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
client/src/main/resources/textures/sample_texture_4.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
client/src/main/resources/textures/sample_texture_5.JPG
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
client/src/main/resources/textures/sample_texture_6.JPEG
Normal file
After Width: | Height: | Size: 6.2 KiB |
@ -1,70 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta><meta:creation-date>2019-03-22T18:11:56.768840842</meta:creation-date><dc:date>2019-05-02T19:33:13.941450858</dc:date><meta:editing-duration>PT3H29M9S</meta:editing-duration><meta:editing-cycles>95</meta:editing-cycles><meta:generator>LibreOffice/6.2.3.2$Linux_X86_64 LibreOffice_project/20$Build-2</meta:generator><dc:title>UI Elemente</dc:title><meta:document-statistic meta:table-count="2" meta:image-count="5" meta:object-count="0" meta:page-count="8" meta:paragraph-count="49" meta:word-count="363" meta:character-count="2509" meta:non-whitespace-character-count="2193"/><meta:user-defined meta:name="Version">1.2.0</meta:user-defined></office:meta>
|
||||
<office:meta><meta:creation-date>2019-03-22T18:11:56.768840842</meta:creation-date><dc:date>2019-05-16T14:23:20.574574983</dc:date><meta:editing-duration>PT3H40M5S</meta:editing-duration><meta:editing-cycles>103</meta:editing-cycles><meta:generator>LibreOffice/6.2.3.2$Linux_X86_64 LibreOffice_project/20$Build-2</meta:generator><dc:title>UI Elemente</dc:title><meta:document-statistic meta:table-count="2" meta:image-count="5" meta:object-count="0" meta:page-count="8" meta:paragraph-count="52" meta:word-count="383" meta:character-count="2658" meta:non-whitespace-character-count="2325"/><meta:user-defined meta:name="Version">1.2.1</meta:user-defined></office:meta>
|
||||
<office:settings>
|
||||
<config:config-item-set config:name="ooo:view-settings">
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">1326</config:config-item>
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">212372</config:config-item>
|
||||
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="ViewAreaWidth" config:type="long">45485</config:config-item>
|
||||
<config:config-item config:name="ViewAreaHeight" config:type="long">21458</config:config-item>
|
||||
<config:config-item config:name="ViewAreaWidth" config:type="long">40924</config:config-item>
|
||||
<config:config-item config:name="ViewAreaHeight" config:type="long">18898</config:config-item>
|
||||
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
|
||||
<config:config-item-map-indexed config:name="Views">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">14240</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">219049</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">11959</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">219731</config:config-item>
|
||||
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">1326</config:config-item>
|
||||
<config:config-item config:name="VisibleRight" config:type="long">45484</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">22782</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">212372</config:config-item>
|
||||
<config:config-item config:name="VisibleRight" config:type="long">40922</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">231269</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ZoomFactor" config:type="short">110</config:config-item>
|
||||
<config:config-item config:name="ZoomFactor" config:type="short">120</config:config-item>
|
||||
<config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
|
||||
</config:config-item-map-entry>
|
||||
</config:config-item-map-indexed>
|
||||
</config:config-item-set>
|
||||
<config:config-item-set config:name="ooo:configuration-settings">
|
||||
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintFaxName" config:type="string"/>
|
||||
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
|
||||
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintFaxName" config:type="string"/>
|
||||
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="DisableOffPagePositioning" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="RsidRoot" config:type="int">192011</config:config-item>
|
||||
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrinterName" config:type="string"/>
|
||||
<config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">4666890</config:config-item>
|
||||
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
|
||||
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintEmptyPages" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
|
||||
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
|
||||
<config:config-item-map-indexed config:name="ForbiddenCharacters">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="Language" config:type="string">de</config:config-item>
|
||||
@ -74,62 +104,32 @@
|
||||
<config:config-item config:name="EndLine" config:type="string"/>
|
||||
</config:config-item-map-entry>
|
||||
</config:config-item-map-indexed>
|
||||
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
|
||||
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
|
||||
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
|
||||
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
|
||||
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
|
||||
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrinterName" config:type="string"/>
|
||||
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
|
||||
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
|
||||
<config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
|
||||
<config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
|
||||
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
|
||||
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
|
||||
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="DisableOffPagePositioning" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">4355840</config:config-item>
|
||||
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintEmptyPages" config:type="boolean">false</config:config-item>
|
||||
</config:config-item-set>
|
||||
</office:settings>
|
||||
<office:scripts>
|
||||
@ -151282,6 +151282,12 @@
|
||||
<style:style style:name="Tabelle3.B4" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Tabelle3.A5" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Tabelle3.B5" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Footer">
|
||||
<style:text-properties officeooo:paragraph-rsid="0010decd"/>
|
||||
</style:style>
|
||||
@ -151340,224 +151346,201 @@
|
||||
<style:style style:name="P18" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="004221e5" officeooo:paragraph-rsid="004221e5"/>
|
||||
</style:style>
|
||||
<style:style style:name="P19" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="0002ee0b" officeooo:paragraph-rsid="0002ee0b"/>
|
||||
<style:style style:name="P19" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="00426493" officeooo:paragraph-rsid="00426493"/>
|
||||
</style:style>
|
||||
<style:style style:name="P20" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
|
||||
<style:text-properties officeooo:rsid="0002ee0b" officeooo:paragraph-rsid="0002ee0b"/>
|
||||
<style:style style:name="P20" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="0046697a" officeooo:paragraph-rsid="0046697a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P21" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="00362d6b" officeooo:paragraph-rsid="002dfe1b"/>
|
||||
<style:text-properties officeooo:rsid="0002ee0b" officeooo:paragraph-rsid="0002ee0b"/>
|
||||
</style:style>
|
||||
<style:style style:name="P22" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:paragraph-rsid="002dfe1b"/>
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
|
||||
<style:text-properties officeooo:rsid="0002ee0b" officeooo:paragraph-rsid="0002ee0b"/>
|
||||
</style:style>
|
||||
<style:style style:name="P23" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="00362d6b" officeooo:paragraph-rsid="002dfe1b"/>
|
||||
</style:style>
|
||||
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:paragraph-rsid="002dfe1b"/>
|
||||
</style:style>
|
||||
<style:style style:name="P25" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:paragraph-properties fo:text-align="end" style:justify-single-word="false"/>
|
||||
<style:text-properties officeooo:rsid="0032709a" officeooo:paragraph-rsid="0032709a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P24" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="0032709a" officeooo:paragraph-rsid="0032709a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P25" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="0032a807" officeooo:paragraph-rsid="0032a807"/>
|
||||
</style:style>
|
||||
<style:style style:name="P26" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P28" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties style:font-name="Fira Mono1" fo:font-size="12pt" officeooo:paragraph-rsid="0036e1f0" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P27" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties style:font-name="Fira Mono1" fo:font-size="12pt" officeooo:rsid="00329f53" officeooo:paragraph-rsid="0036e1f0" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P28" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties style:font-name="Fira Mono1" fo:font-size="12pt" officeooo:rsid="0033d90c" officeooo:paragraph-rsid="0036e1f0" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P29" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P31" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties style:font-name="Fira Mono1" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P30" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="0038b1c6" officeooo:paragraph-rsid="0038b1c6"/>
|
||||
</style:style>
|
||||
<style:style style:name="P31" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="0038fb8a" officeooo:paragraph-rsid="0038fb8a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P32" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="003d714e" officeooo:paragraph-rsid="003d714e"/>
|
||||
</style:style>
|
||||
<style:style style:name="P33" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="003eefc4" officeooo:paragraph-rsid="003eefc4"/>
|
||||
</style:style>
|
||||
<style:style style:name="P34" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="004221e5" officeooo:paragraph-rsid="004221e5"/>
|
||||
</style:style>
|
||||
<style:style style:name="P35" style:family="paragraph" style:parent-style-name="Contents_20_Heading">
|
||||
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="00427700" officeooo:paragraph-rsid="00427700"/>
|
||||
</style:style>
|
||||
<style:style style:name="P38" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="0046697a" officeooo:paragraph-rsid="0046697a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P39" style:family="paragraph" style:parent-style-name="Contents_20_Heading">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
</style:style>
|
||||
<style:style style:name="P36" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P40" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
</style:style>
|
||||
<style:style style:name="P37" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P41" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="00085afb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P38" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P42" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:rsid="0036e1f0" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P39" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P43" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:rsid="0038b1c6" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P40" style:family="paragraph" style:parent-style-name="Contents_20_1">
|
||||
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Contents_20_1">
|
||||
<style:paragraph-properties>
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="17.006cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P41" style:family="paragraph" style:parent-style-name="Contents_20_2">
|
||||
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="Contents_20_2">
|
||||
<style:paragraph-properties>
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="16.506cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P42" style:family="paragraph" style:parent-style-name="Title">
|
||||
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="Title">
|
||||
<style:text-properties officeooo:paragraph-rsid="0004b30c"/>
|
||||
</style:style>
|
||||
<style:style style:name="P43" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<style:style style:name="P47" style:family="paragraph" style:parent-style-name="Table_20_Heading">
|
||||
<loext:graphic-properties draw:fill="solid" draw:fill-color="#ffde03" draw:opacity="100%"/>
|
||||
<style:paragraph-properties fo:background-color="#ffde03"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="002dfe1b"/>
|
||||
</style:style>
|
||||
<style:style style:name="P44" style:family="paragraph" style:parent-style-name="Title" style:master-page-name="">
|
||||
<style:style style:name="P48" style:family="paragraph" style:parent-style-name="Title" style:master-page-name="">
|
||||
<style:paragraph-properties style:page-number="auto"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="0004b30c"/>
|
||||
</style:style>
|
||||
<style:style style:name="P45" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P49" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:paragraph-rsid="00085afb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P46" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P50" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:rsid="0032a807" officeooo:paragraph-rsid="0032a807"/>
|
||||
</style:style>
|
||||
<style:style style:name="P47" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P51" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:rsid="0034ee36" officeooo:paragraph-rsid="0034ee36"/>
|
||||
</style:style>
|
||||
<style:style style:name="P48" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P52" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:rsid="0036e1f0" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P49" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P53" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:line-height="100%">
|
||||
<style:tab-stops/>
|
||||
</style:paragraph-properties>
|
||||
<style:text-properties officeooo:rsid="0036e1f0" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P50" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P54" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:rsid="002b4dc2" officeooo:paragraph-rsid="002b4dc2"/>
|
||||
</style:style>
|
||||
<style:style style:name="P51" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P55" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:rsid="00329f53" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P52" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P56" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:paragraph-rsid="0041ca2e"/>
|
||||
</style:style>
|
||||
<style:style style:name="P53" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P57" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="002aa4d8" officeooo:paragraph-rsid="002aa4d8"/>
|
||||
</style:style>
|
||||
<style:style style:name="P54" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P58" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P55" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P59" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="0036e1f0" officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P56" style:family="paragraph" style:parent-style-name="Heading_20_4">
|
||||
<style:style style:name="P60" style:family="paragraph" style:parent-style-name="Heading_20_4">
|
||||
<style:text-properties style:font-name="Fira Mono1" fo:font-size="12pt" officeooo:paragraph-rsid="0036e1f0" style:font-size-asian="12pt" style:font-size-complex="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P57" style:family="paragraph" style:parent-style-name="Heading_20_4">
|
||||
<style:style style:name="P61" style:family="paragraph" style:parent-style-name="Heading_20_4">
|
||||
<style:text-properties officeooo:paragraph-rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P58" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:style style:name="P62" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:rsid="0036e1f0"/>
|
||||
</style:style>
|
||||
<style:style style:name="P59" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:style style:name="P63" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="P60" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:style style:name="P64" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false"/>
|
||||
<style:text-properties fo:font-size="12pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="P61" style:family="paragraph" style:parent-style-name="Quelltext">
|
||||
<style:style style:name="P65" style:family="paragraph" style:parent-style-name="Quelltext">
|
||||
<style:text-properties officeooo:rsid="003eefc4" officeooo:paragraph-rsid="003eefc4"/>
|
||||
</style:style>
|
||||
<style:style style:name="P62" style:family="paragraph" style:parent-style-name="Quelltext">
|
||||
<style:style style:name="P66" style:family="paragraph" style:parent-style-name="Quelltext">
|
||||
<style:text-properties officeooo:rsid="0041ca2e" officeooo:paragraph-rsid="0041ca2e"/>
|
||||
</style:style>
|
||||
<style:style style:name="P63" style:family="paragraph" style:parent-style-name="Text_20_body" style:list-style-name="">
|
||||
<style:style style:name="P67" style:family="paragraph" style:parent-style-name="Text_20_body" style:list-style-name="">
|
||||
<style:paragraph-properties>
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="14.203cm"/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P64" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="00426493" officeooo:paragraph-rsid="00426493"/>
|
||||
</style:style>
|
||||
<style:style style:name="P65" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="0041ca2e" officeooo:paragraph-rsid="0041ca2e"/>
|
||||
</style:style>
|
||||
<style:style style:name="P66" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="003fa21a" officeooo:paragraph-rsid="003fa21a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P67" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="004221e5" officeooo:paragraph-rsid="004221e5"/>
|
||||
</style:style>
|
||||
<style:style style:name="P68" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="00427700" officeooo:paragraph-rsid="00427700"/>
|
||||
</style:style>
|
||||
<style:style style:name="P69" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P68" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:paragraph-rsid="00085afb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P70" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P69" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:rsid="0032a807" officeooo:paragraph-rsid="0032a807"/>
|
||||
</style:style>
|
||||
<style:style style:name="P70" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
</style:style>
|
||||
<style:style style:name="P71" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
</style:style>
|
||||
<style:style style:name="P72" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="00085afb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P73" style:family="paragraph" style:parent-style-name="Title" style:master-page-name="First_20_Page">
|
||||
<style:style style:name="P72" style:family="paragraph" style:parent-style-name="Title" style:master-page-name="First_20_Page">
|
||||
<style:paragraph-properties style:page-number="auto"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="0004b30c"/>
|
||||
</style:style>
|
||||
<style:style style:name="P74" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P73" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:paragraph-rsid="0041ca2e"/>
|
||||
</style:style>
|
||||
<style:style style:name="P75" style:family="paragraph" style:parent-style-name="Contents_20_Heading">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
</style:style>
|
||||
<style:style style:name="P76" style:family="paragraph" style:parent-style-name="Contents_20_1">
|
||||
<style:paragraph-properties>
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="17.006cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P77" style:family="paragraph" style:parent-style-name="Contents_20_2">
|
||||
<style:paragraph-properties>
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="16.506cm" style:type="right" style:leader-style="dotted" style:leader-text="."/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P78" style:family="paragraph" style:parent-style-name="Quelltext">
|
||||
<style:text-properties officeooo:rsid="003eefc4" officeooo:paragraph-rsid="003eefc4"/>
|
||||
</style:style>
|
||||
<style:style style:name="P79" style:family="paragraph">
|
||||
<style:style style:name="P74" style:family="paragraph">
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
</style:style>
|
||||
<style:style style:name="P80" style:family="paragraph">
|
||||
<style:style style:name="P75" style:family="paragraph">
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
<style:text-properties fo:font-size="12pt"/>
|
||||
</style:style>
|
||||
@ -151633,14 +151616,20 @@
|
||||
<style:style style:name="T24" style:family="text">
|
||||
<style:text-properties officeooo:rsid="00426493"/>
|
||||
</style:style>
|
||||
<style:style style:name="T25" style:family="text">
|
||||
<style:text-properties officeooo:rsid="00432291"/>
|
||||
</style:style>
|
||||
<style:style style:name="T26" style:family="text">
|
||||
<style:text-properties officeooo:rsid="004404ab"/>
|
||||
</style:style>
|
||||
<style:style style:name="T27" style:family="text">
|
||||
<style:text-properties officeooo:rsid="0047360a"/>
|
||||
</style:style>
|
||||
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
|
||||
<style:graphic-properties style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
<style:graphic-properties style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
</style:style>
|
||||
<style:style style:name="fr2" style:family="graphic" style:parent-style-name="Graphics">
|
||||
<style:graphic-properties style:vertical-pos="top" style:vertical-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
</style:style>
|
||||
<style:style style:name="fr3" style:family="graphic" style:parent-style-name="Graphics">
|
||||
<style:graphic-properties style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
<style:graphic-properties style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
</style:style>
|
||||
<style:style style:name="Sect1" style:family="section">
|
||||
<style:section-properties style:editable="false">
|
||||
@ -151669,7 +151658,7 @@
|
||||
<style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.265cm" fo:min-width="5.681cm" fo:wrap-option="no-wrap" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" style:flow-with-text="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="gr8" style:family="graphic">
|
||||
<style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.367cm" fo:min-width="6.158cm" fo:wrap-option="no-wrap" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" style:flow-with-text="false"/>
|
||||
<style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.108cm" fo:min-width="8.276cm" fo:wrap-option="no-wrap" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" style:flow-with-text="false"/>
|
||||
</style:style>
|
||||
<style:style style:name="gr9" style:family="graphic">
|
||||
<style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.639cm" fo:min-width="5.727cm" fo:wrap-option="no-wrap" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" draw:wrap-influence-on-position="once-concurrent" style:flow-with-text="false"/>
|
||||
@ -151726,7 +151715,7 @@
|
||||
<office:master-styles>
|
||||
<style:master-page style:name="Standard" style:page-layout-name="pm1">
|
||||
<style:footer>
|
||||
<text:p text:style-name="P1"><text:span text:style-name="T1">TextureSync</text:span> <text:tab/><text:title>UI Elemente</text:title> <text:span text:style-name="T2">⸬ </text:span><text:span text:style-name="T1">Version </text:span><text:user-defined style:data-style-name="N0" text:name="Version">1.2.0</text:user-defined><text:tab/><text:span text:style-name="T3">Seite</text:span> <text:page-number text:select-page="current">7</text:page-number>/<text:page-count>8</text:page-count></text:p>
|
||||
<text:p text:style-name="P1"><text:span text:style-name="T1">TextureSync</text:span> <text:tab/><text:title>UI Elemente</text:title> <text:span text:style-name="T2">⸬ </text:span><text:span text:style-name="T1">Version </text:span><text:user-defined style:data-style-name="N0" text:name="Version">1.2.1</text:user-defined><text:tab/><text:span text:style-name="T3">Seite</text:span> <text:page-number text:select-page="current">8</text:page-number>/<text:page-count>8</text:page-count></text:p>
|
||||
</style:footer>
|
||||
</style:master-page>
|
||||
<style:master-page style:name="First_20_Page" style:display-name="First Page" style:page-layout-name="pm2" style:next-style-name="Standard"/>
|
||||
@ -151743,8 +151732,8 @@
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
|
||||
</text:sequence-decls>
|
||||
<text:p text:style-name="P73"/>
|
||||
<text:p text:style-name="P42"/>
|
||||
<text:p text:style-name="P72"/>
|
||||
<text:p text:style-name="P46"/>
|
||||
<text:p text:style-name="Title"><text:title>UI Elemente</text:title></text:p>
|
||||
<text:p text:style-name="Subtitle">TextureSync</text:p>
|
||||
<text:p text:style-name="Text_20_body"/>
|
||||
@ -151765,41 +151754,41 @@
|
||||
<table:table-column table:style-name="Tabelle1.B"/>
|
||||
<table:table-row table:style-name="Tabelle1.1">
|
||||
<table:table-cell table:style-name="Tabelle1.A1" office:value-type="string">
|
||||
<text:p text:style-name="P20">Version</text:p>
|
||||
<text:p text:style-name="P22">Version</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle1.B1" office:value-type="string">
|
||||
<text:p text:style-name="P19"><text:user-defined style:data-style-name="N0" text:name="Version">1.2.0</text:user-defined></text:p>
|
||||
<text:p text:style-name="P21"><text:user-defined style:data-style-name="N0" text:name="Version">1.2.1</text:user-defined></text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Tabelle1.1">
|
||||
<table:table-cell table:style-name="Tabelle1.A2" office:value-type="string">
|
||||
<text:p text:style-name="P20">Datum</text:p>
|
||||
<text:p text:style-name="P22">Datum</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle1.B3" office:value-type="string">
|
||||
<text:p text:style-name="P32">25.04.19</text:p>
|
||||
<text:p text:style-name="P34">25.04.19</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Tabelle1.1">
|
||||
<table:table-cell table:style-name="Tabelle1.A2" office:value-type="string">
|
||||
<text:p text:style-name="P23">Autor</text:p>
|
||||
<text:p text:style-name="P25">Autor</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle1.B3" office:value-type="string">
|
||||
<text:p text:style-name="P33">Jannik Seiler</text:p>
|
||||
<text:p text:style-name="P35">Jannik Seiler</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Tabelle1.1">
|
||||
<table:table-cell table:style-name="Tabelle1.A2" office:value-type="string">
|
||||
<text:p text:style-name="P20">Projektmitglieder</text:p>
|
||||
<text:p text:style-name="P22">Projektmitglieder</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle1.B4" office:value-type="string">
|
||||
<text:p text:style-name="P19">Hendrik Schutter,</text:p>
|
||||
<text:p text:style-name="P19">Lukas Fürderer,</text:p>
|
||||
<text:p text:style-name="P19">Robin Willmann,</text:p>
|
||||
<text:p text:style-name="P19">Jannik Seiler</text:p>
|
||||
<text:p text:style-name="P21">Hendrik Schutter,</text:p>
|
||||
<text:p text:style-name="P21">Lukas Fürderer,</text:p>
|
||||
<text:p text:style-name="P21">Robin Willmann,</text:p>
|
||||
<text:p text:style-name="P21">Jannik Seiler</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
<text:h text:style-name="P63" text:outline-level="1"/>
|
||||
<text:h text:style-name="P67" text:outline-level="1"/>
|
||||
<text:table-of-content text:style-name="Sect1" text:protected="true" text:name="Inhaltsverzeichnis1">
|
||||
<text:table-of-content-source text:outline-level="2">
|
||||
<text:index-title-template text:style-name="Contents_20_Heading">Inhaltsverzeichnis</text:index-title-template>
|
||||
@ -151886,24 +151875,24 @@
|
||||
</text:table-of-content-source>
|
||||
<text:index-body>
|
||||
<text:index-title text:style-name="Sect1" text:name="Inhaltsverzeichnis1_Head">
|
||||
<text:p text:style-name="P35">Inhaltsverzeichnis</text:p>
|
||||
<text:p text:style-name="P39">Inhaltsverzeichnis</text:p>
|
||||
</text:index-title>
|
||||
<text:p text:style-name="P40"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc122_1745181160" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">1 Einleitung<text:tab/>3</text:a></text:p>
|
||||
<text:p text:style-name="P40"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc145_4018349791" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2 Genereller Aufbau<text:tab/>3</text:a></text:p>
|
||||
<text:p text:style-name="P41"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc440_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.1 Erster Start<text:tab/>4</text:a></text:p>
|
||||
<text:p text:style-name="P41"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc442_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.2 Hauptfenster<text:tab/>5</text:a></text:p>
|
||||
<text:p text:style-name="P41"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc444_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.3 Dialog<text:tab/>6</text:a></text:p>
|
||||
<text:p text:style-name="P41"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc446_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.4 Textur importieren<text:tab/>7</text:a></text:p>
|
||||
<text:p text:style-name="P40"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc752_3921231312" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">3 Changelog<text:tab/>8</text:a></text:p>
|
||||
<text:p text:style-name="P44"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc122_1745181160" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">1 Einleitung<text:tab/>3</text:a></text:p>
|
||||
<text:p text:style-name="P44"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc145_4018349791" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2 Genereller Aufbau<text:tab/>3</text:a></text:p>
|
||||
<text:p text:style-name="P45"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc440_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.1 Erster Start<text:tab/>4</text:a></text:p>
|
||||
<text:p text:style-name="P45"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc442_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.2 Hauptfenster<text:tab/>5</text:a></text:p>
|
||||
<text:p text:style-name="P45"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc444_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.3 Dialog<text:tab/>6</text:a></text:p>
|
||||
<text:p text:style-name="P45"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc446_4157948899" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">2.4 Textur importieren<text:tab/>7</text:a></text:p>
|
||||
<text:p text:style-name="P44"><text:a xlink:type="simple" xlink:href="#__RefHeading___Toc752_3921231312" text:style-name="Index_20_Link" text:visited-style-name="Index_20_Link">3 Changelog<text:tab/>8</text:a></text:p>
|
||||
</text:index-body>
|
||||
</text:table-of-content>
|
||||
<text:h text:style-name="P45" text:outline-level="1" text:is-list-header="true"/>
|
||||
<text:h text:style-name="P37" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc122_1745181160"/>Einleitung<text:bookmark-end text:name="__RefHeading___Toc122_1745181160"/></text:h>
|
||||
<text:p text:style-name="P64">Das Ziel des Feindesign der UI-Elemente ist festzulegen welche Eingabe – und Ausgabelemente geeignet sind. </text:p>
|
||||
<text:p text:style-name="P64">Die Architektur der UI wie auch die Navigation im Client werden dadurch definiert.</text:p>
|
||||
<text:h text:style-name="P46" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc145_4018349791"/>Genereller Aufbau<text:bookmark-end text:name="__RefHeading___Toc145_4018349791"/></text:h>
|
||||
<text:p text:style-name="P62">Die verschiedenen Views werden wiederum auf einer „RootView“ <text:s/>angebracht. <text:span text:style-name="T24">Das Diagramm zeigt die Darstellung der MainView und deren Elemente.</text:span></text:p>
|
||||
<text:p text:style-name="P61"><draw:frame draw:style-name="fr3" draw:name="Bild5" text:anchor-type="paragraph" svg:x="5.159cm" svg:y="0.18cm" svg:width="6.632cm" svg:height="10.472cm" draw:z-index="18"><draw:image loext:mime-type="image/png">
|
||||
<text:h text:style-name="P49" text:outline-level="1" text:is-list-header="true"/>
|
||||
<text:h text:style-name="P41" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc122_1745181160"/>Einleitung<text:bookmark-end text:name="__RefHeading___Toc122_1745181160"/></text:h>
|
||||
<text:p text:style-name="P19">Das Ziel des Feindesign der UI-Elemente ist <text:span text:style-name="T26">es </text:span>festzulegen, welche Eingabe – und Ausgabelemente geeignet sind. </text:p>
|
||||
<text:p text:style-name="P19">Die Architektur der UI wie auch die Navigation im Client werden dadurch definiert.</text:p>
|
||||
<text:h text:style-name="P50" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc145_4018349791"/>Genereller Aufbau<text:bookmark-end text:name="__RefHeading___Toc145_4018349791"/></text:h>
|
||||
<text:p text:style-name="P66">Die verschiedenen Views werden wiederum auf einer „RootView“ <text:s/>angebracht. <text:span text:style-name="T24">Das Diagramm zeigt die Darstellung der MainView und deren Elemente.</text:span></text:p>
|
||||
<text:p text:style-name="P65"><draw:frame draw:style-name="fr1" draw:name="Bild5" text:anchor-type="paragraph" svg:x="5.159cm" svg:y="0.18cm" svg:width="6.632cm" svg:height="10.472cm" draw:z-index="18"><draw:image loext:mime-type="image/png">
|
||||
<office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAcgAAALQCAYAAAAUzOCBAAAABHNCSVQICAgIfAhkiAAABQZ6
|
||||
VFh0bXhHcmFwaE1vZGVsAABNVcmSg0oO/BpHzBzawb4cAQNm37Hh8gIoMPuOwXz9K0/3YS5Q
|
||||
SKkkpVKpLrjQHUXV5hcM6QZQFVUOLvjtgmEYgrI/CPmDYD5KX3AOY+EDQa4og8TQDfHlsKy/
|
||||
@ -152662,19 +152651,19 @@
|
||||
</office:binary-data>
|
||||
</draw:image>
|
||||
</draw:frame></text:p>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P61"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:p text:style-name="P65"/>
|
||||
<text:h text:style-name="Heading_20_2" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc440_4157948899"/><text:soft-page-break/>Erster Start<text:bookmark-end text:name="__RefHeading___Toc440_4157948899"/></text:h>
|
||||
<text:p text:style-name="P16"><draw:frame draw:style-name="fr1" draw:name="Bild1" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="0"><draw:image loext:mime-type="image/png">
|
||||
<text:p text:style-name="P16"><draw:frame draw:style-name="fr2" draw:name="Bild1" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="0"><draw:image loext:mime-type="image/png">
|
||||
<office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAvAAAAGQCAYAAADIulS9AAAgAElEQVR4nOzdd3gVddrG8ScF
|
||||
EiAkhNBbaFJDDTWACb2XSEB6pHdBLChFsBdUQBcRBEXW7gq4iIotCOKyiuKuuxZkRUXgVVFU
|
||||
VJB2v3/gnHCSk5CQkJno93NdzyVkZn7zzBwMN5OZ39jnn3+uihUrqlSpUipVqpSKFy/u+3VB
|
||||
@ -152996,11 +152985,11 @@
|
||||
nE6nj6lpmqiqSpL0hnLOg/+vS3q9nHPsdruf3/D9fo8v3cTSQOozONUAAAAASUVORK5CYII=
|
||||
</office:binary-data>
|
||||
</draw:image>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="1" draw:name="Form1" draw:style-name="gr1" draw:text-style-name="P79" svg:width="5.22cm" svg:height="2.07cm" svg:x="0.409cm" svg:y="1.104cm">
|
||||
<text:p text:style-name="P79">JFXTextfield, IPv4 Adresse</text:p>
|
||||
<text:p text:style-name="P79">kann eingegeben werden,</text:p>
|
||||
<text:p text:style-name="P79">mit Enter wird zum</text:p>
|
||||
<text:p text:style-name="P79">Server verbunden</text:p>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="1" draw:name="Form1" draw:style-name="gr1" draw:text-style-name="P74" svg:width="5.22cm" svg:height="2.07cm" svg:x="0.409cm" svg:y="1.104cm">
|
||||
<text:p text:style-name="P74">JFXTextfield, IPv4 Adresse</text:p>
|
||||
<text:p text:style-name="P74">kann eingegeben werden,</text:p>
|
||||
<text:p text:style-name="P74">mit Enter wird zum</text:p>
|
||||
<text:p text:style-name="P74">Server verbunden</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="23584.8648648649 46806.132879046" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -153046,9 +153035,9 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="2" draw:name="Form3" draw:style-name="gr2" draw:text-style-name="P79" svg:width="4.933cm" svg:height="1.853cm" svg:x="11.737cm" svg:y="0.718cm">
|
||||
<text:p text:style-name="P79">JFXSpinner, dreht sich</text:p>
|
||||
<text:p text:style-name="P79">während des Verbindens</text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="2" draw:name="Form3" draw:style-name="gr2" draw:text-style-name="P74" svg:width="4.933cm" svg:height="1.853cm" svg:x="11.737cm" svg:y="0.718cm">
|
||||
<text:p text:style-name="P74">JFXSpinner, dreht sich</text:p>
|
||||
<text:p text:style-name="P74">während des Verbindens</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="-10950.589917769 33211.7982873454" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -153094,9 +153083,9 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="3" draw:name="Form4" draw:style-name="gr3" draw:text-style-name="P80" svg:width="5.294cm" svg:height="1.631cm" svg:x="11.497cm" svg:y="6.505cm">
|
||||
<text:p text:style-name="P79"><text:span text:style-name="T23">JFXButton, verbinde mit der</text:span></text:p>
|
||||
<text:p text:style-name="P79"><text:span text:style-name="T23">angegebenen IPv4 Adresse</text:span></text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="3" draw:name="Form4" draw:style-name="gr3" draw:text-style-name="P75" svg:width="5.294cm" svg:height="1.631cm" svg:x="11.497cm" svg:y="6.505cm">
|
||||
<text:p text:style-name="P74"><text:span text:style-name="T23">JFXButton, verbindet mit der</text:span></text:p>
|
||||
<text:p text:style-name="P74"><text:span text:style-name="T23">angegebenen IPv4 Adresse</text:span></text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="-6360.55962691539 490.378378378378" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -153143,8 +153132,8 @@
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape></text:p>
|
||||
<text:p text:style-name="P16">Die StartupView erscheint beim ersten Start des Clients, oder wenn keine Serveradresse gespeichert <text:s text:c="2"/>ist <text:span text:style-name="T22">beziehungsweise</text:span> kein Server im Netzwerk gefunden <text:span text:style-name="T22">wurde</text:span>.</text:p>
|
||||
<text:p text:style-name="P64">Der Nutzer kann die Adresse des lokalen Servers eingeben und mittels einem Button die Verbindung aufbauen. Während des Verbindungsprozesses wird ein Spinner eingeblendet und Infotext entsprechend angepasst.</text:p>
|
||||
<text:p text:style-name="P16">Die StartupView erscheint beim ersten Start des Clients oder wenn keine Serveradresse gespeichert <text:s text:c="2"/>ist, <text:span text:style-name="T22">beziehungsweise</text:span> kein Server im Netzwerk gefunden <text:span text:style-name="T22">wurde</text:span>.</text:p>
|
||||
<text:p text:style-name="P19">Der Nutzer kann die Adresse des lokalen Servers eingeben und mittels einem Button die Verbindung aufbauen. Während des Verbindungsprozesses wird ein Spinner eingeblendet und Infotext entsprechend angepasst.</text:p>
|
||||
<text:p text:style-name="Text_20_body"/>
|
||||
<text:p text:style-name="Text_20_body"/>
|
||||
<text:p text:style-name="Text_20_body"/>
|
||||
@ -154475,11 +154464,11 @@
|
||||
QmCC
|
||||
</office:binary-data>
|
||||
</draw:image>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="5" draw:name="Form2" draw:style-name="gr4" draw:text-style-name="P79" svg:width="5.342cm" svg:height="2.153cm" svg:x="0.48cm" svg:y="1.639cm">
|
||||
<text:p text:style-name="P79">JFXTextfield, hier lassen sich</text:p>
|
||||
<text:p text:style-name="P79">Die Texturen nach Name, </text:p>
|
||||
<text:p text:style-name="P79">Tags, Auflösung und </text:p>
|
||||
<text:p text:style-name="P79">Einfügedatum durchsuchen</text:p>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="5" draw:name="Form2" draw:style-name="gr4" draw:text-style-name="P74" svg:width="5.342cm" svg:height="2.153cm" svg:x="0.48cm" svg:y="1.639cm">
|
||||
<text:p text:style-name="P74">JFXTextfield, hier lassen sich</text:p>
|
||||
<text:p text:style-name="P74">Die Texturen nach Name, </text:p>
|
||||
<text:p text:style-name="P74">Tags, Auflösung und </text:p>
|
||||
<text:p text:style-name="P74">Einfügedatum durchsuchen</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="8272.03697589964 -7111.54791154791" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -154525,10 +154514,10 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="6" draw:name="Form5" draw:style-name="gr5" draw:text-style-name="P79" svg:width="5.846cm" svg:height="1.816cm" svg:x="6.565cm" svg:y="1.616cm">
|
||||
<text:p text:style-name="P79">3DPreview, zeigt einen Würfel</text:p>
|
||||
<text:p text:style-name="P79">Mit der aktuell </text:p>
|
||||
<text:p text:style-name="P79">ausgewählten Textur an</text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="6" draw:name="Form5" draw:style-name="gr5" draw:text-style-name="P74" svg:width="5.846cm" svg:height="1.816cm" svg:x="6.565cm" svg:y="1.616cm">
|
||||
<text:p text:style-name="P74">3DPreview, zeigt einen Würfel</text:p>
|
||||
<text:p text:style-name="P74">Mit der aktuell </text:p>
|
||||
<text:p text:style-name="P74">ausgewählten Textur an</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="25327.0588235294 12163.1067961165" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -154574,10 +154563,10 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="7" draw:name="Form6" draw:style-name="gr6" draw:text-style-name="P79" svg:width="6.712cm" svg:height="1.479cm" svg:x="1.635cm" svg:y="10.134cm">
|
||||
<text:p text:style-name="P79">Gridpane, enthält alle </text:p>
|
||||
<text:p text:style-name="P79">TexturGUI-Elemente mit 2D-Preview</text:p>
|
||||
<text:p text:style-name="P79">und Name</text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="7" draw:name="Form6" draw:style-name="gr6" draw:text-style-name="P74" svg:width="6.712cm" svg:height="1.479cm" svg:x="1.635cm" svg:y="10.134cm">
|
||||
<text:p text:style-name="P74">Gridpane, enthält alle </text:p>
|
||||
<text:p text:style-name="P74">TexturGUI-Elemente mit 2D-Preview</text:p>
|
||||
<text:p text:style-name="P74">und Name</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="-380.241723594325 -37046.9606674613" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -154623,10 +154612,10 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="9" draw:name="Form8" draw:style-name="gr8" draw:text-style-name="P79" svg:width="6.159cm" svg:height="1.366cm" svg:x="6.301cm" svg:y="4.688cm">
|
||||
<text:p text:style-name="P79">DetailView, zeigt Informationen</text:p>
|
||||
<text:p text:style-name="P79">wie Name, Tags und metadaten an</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="24810.3092783505 13963.3548387097" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="9" draw:name="Form8" draw:style-name="gr8" draw:text-style-name="P74" svg:width="8.275cm" svg:height="1.107cm" svg:x="4.618cm" svg:y="5.099cm">
|
||||
<text:p text:style-name="P74">DetailView, zeigt Informationen</text:p>
|
||||
<text:p text:style-name="P74">wie Name, Tags, Auflösung und Einfügedatum</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="22852.1739130435 9252.22929936306" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
<draw:equation draw:name="f2" draw:formula="if(?f18 ,$0 ,0)"/>
|
||||
@ -154672,9 +154661,9 @@
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape></text:p>
|
||||
<text:p text:style-name="Text_20_body"><draw:custom-shape text:anchor-type="paragraph" draw:z-index="8" draw:name="Form7" draw:style-name="gr7" draw:text-style-name="P80" svg:width="5.68cm" svg:height="1.265cm" svg:x="10.437cm" svg:y="-0.307cm">
|
||||
<text:p text:style-name="P79"><text:span text:style-name="T23">JFXButton, hierüber lässt sich </text:span></text:p>
|
||||
<text:p text:style-name="P79"><text:span text:style-name="T23">die ImportView aufrufen</text:span></text:p>
|
||||
<text:p text:style-name="Text_20_body"><draw:custom-shape text:anchor-type="paragraph" draw:z-index="8" draw:name="Form7" draw:style-name="gr7" draw:text-style-name="P75" svg:width="5.68cm" svg:height="1.265cm" svg:x="10.437cm" svg:y="-0.307cm">
|
||||
<text:p text:style-name="P74"><text:span text:style-name="T23">JFXButton, hierüber lässt sich </text:span></text:p>
|
||||
<text:p text:style-name="P74"><text:span text:style-name="T23">die ImportView aufrufen</text:span></text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="21586.5880161441 -19072.9805013928" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -154722,11 +154711,12 @@
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape></text:p>
|
||||
<text:p text:style-name="Text_20_body"/>
|
||||
<text:p text:style-name="P17">Die MainView wird angezeigt sobald das Programm fertig initialisiert ist. Sie enthält eine Scrollpane mit einer Gridpane welche alle <text:span text:style-name="T24">gefunden</text:span> Texturen anzeigt. Die DetailView besteht aus einer 3D-Preview und einer Anzeige von Name, Tags, <text:span text:style-name="T24">Auflösung und dem Einfügedatum</text:span>. Die Tags werden in einer ChipView dargestellt. <text:span text:style-name="T24">Das editiertren der Tags ist direkt in der ChipView möglich.</text:span></text:p>
|
||||
<text:p text:style-name="P64">Über ein Button lässt sich die komplette Textur löschen, hierfür wird der Lösch-Dialog aufgerufen.</text:p>
|
||||
<text:p text:style-name="P64">Ein Hinzufügen-Symbol wie ein „+“ startet den Vorgang für das Hinzufügen der Textur.</text:p>
|
||||
<text:h text:style-name="P52" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc444_4157948899"/><text:soft-page-break/>Dialog<text:bookmark-end text:name="__RefHeading___Toc444_4157948899"/></text:h>
|
||||
<text:p text:style-name="P18"><draw:frame draw:style-name="fr1" draw:name="Bild3" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.802cm" draw:z-index="10"><draw:image loext:mime-type="image/png">
|
||||
<text:p text:style-name="P17">Die MainView wird angezeigt sobald das Programm fertig initialisiert ist. Sie enthält eine Scrollpane mit einer Gridpane, welche alle <text:span text:style-name="T24">gefunden</text:span> Texturen anzeigt. Die DetailView besteht aus einer 3D-Preview und einer Anzeige von Name, Tags, <text:span text:style-name="T24">Auflösung und dem Einfügedatum</text:span>. Die Tags werden in einer ChipView dargestellt. <text:span text:style-name="T24">Das Editieren der Tags ist direkt in der ChipView möglich.</text:span></text:p>
|
||||
<text:p text:style-name="P19">Über ein<text:span text:style-name="T25">en</text:span> Button lässt sich die komplette Textur löschen, hierfür wird der Lösch-Dialog aufgerufen.</text:p>
|
||||
<text:p text:style-name="P19">Ein Hinzufügen-Symbol wie ein „+“ startet den Vorgang für das Hinzufügen der Textur.</text:p>
|
||||
<text:p text:style-name="P20">Das Exportiren einer Textur erfolgt über das Kontextmenü welches beim Rechts-Klick auf die Textur erscheint.</text:p>
|
||||
<text:h text:style-name="P56" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc444_4157948899"/><text:soft-page-break/>Dialog<text:bookmark-end text:name="__RefHeading___Toc444_4157948899"/></text:h>
|
||||
<text:p text:style-name="P18"><draw:frame draw:style-name="fr2" draw:name="Bild3" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.802cm" draw:z-index="10"><draw:image loext:mime-type="image/png">
|
||||
<office:binary-data>iVBORw0KGgoAAAANSUhEUgAABIUAAAKbCAYAAABrQDp8AAAgAElEQVR4nOzde5hbVb3w8bQF
|
||||
W6BQtOAj6gMeq+mFMpRepjBtpzLTmcxkppOZzDWZmWQmmQRBON4O6MHL6+ODHLxwUI9aL+hR
|
||||
vMtR9BzEI94QRUXwcMALgoigFkQUUBGOBfp7/6g7JJmdZO+dvbJWsr/reT7PA512kmlnlb2+
|
||||
@ -155783,8 +155773,8 @@
|
||||
TkSuQmCC
|
||||
</office:binary-data>
|
||||
</draw:image>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="12" draw:name="Form9" draw:style-name="gr9" draw:text-style-name="P79" svg:width="5.726cm" svg:height="1.638cm" svg:x="0.769cm" svg:y="5.366cm">
|
||||
<text:p text:style-name="P79">Dialog, besteht aus Heading <text:line-break/>und Description</text:p>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="12" draw:name="Form9" draw:style-name="gr9" draw:text-style-name="P74" svg:width="5.726cm" svg:height="1.638cm" svg:x="0.769cm" svg:y="5.366cm">
|
||||
<text:p text:style-name="P74">Dialog, besteht aus Heading <text:line-break/>und Description</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="27666.8925161688 -11578.9020452099" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -155830,8 +155820,8 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="14" draw:name="Form11" draw:style-name="gr11" draw:text-style-name="P79" svg:width="4.5cm" svg:height="1.407cm" svg:x="9.379cm" svg:y="6.883cm">
|
||||
<text:p text:style-name="P79">JFXButton, abbrechen <text:line-break/>oder ausführen</text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="14" draw:name="Form11" draw:style-name="gr11" draw:text-style-name="P74" svg:width="4.5cm" svg:height="1.407cm" svg:x="9.379cm" svg:y="6.883cm">
|
||||
<text:p text:style-name="P74">JFXButton, abbrechen <text:line-break/>oder ausführen</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="6940.43887147335 -15320.3007518797" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -155879,10 +155869,11 @@
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape></text:p>
|
||||
<text:p text:style-name="P18">Die DialogView zeigt eine Meldung bestehend aus Heading und Description an. Über einen Cancel-Button lässt sich der angezeigte Vorgang abbrechen, über einen Okay-Button bestätigen.</text:p>
|
||||
<text:p text:style-name="P64">Der Dialog wird zum Beispiel für das Bestätigen des Löschens oder bei Fehlermeldungen eingesetzt.</text:p>
|
||||
<text:p text:style-name="P19">Der Dialog wird zum Beispiel für das Bestätigen des Löschens oder bei Fehlermeldungen eingesetzt.</text:p>
|
||||
<text:p text:style-name="P18"/>
|
||||
<text:p text:style-name="P18"/>
|
||||
<text:h text:style-name="Heading_20_2" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc446_4157948899"/><text:soft-page-break/>Textur importieren<text:bookmark-end text:name="__RefHeading___Toc446_4157948899"/></text:h>
|
||||
<text:p text:style-name="P18"><draw:frame draw:style-name="fr1" draw:name="Bild4" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="11"><draw:image loext:mime-type="image/png">
|
||||
<text:p text:style-name="P18"><draw:frame draw:style-name="fr2" draw:name="Bild4" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="11"><draw:image loext:mime-type="image/png">
|
||||
<office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAvAAAAGQCAYAAADIulS9AAAgAElEQVR4nOzdeVhV1eI+8MWM
|
||||
zAioIII5oowKioAKkkqO4DwWlkNqGta362w4p+kNNUstNfNWzmCOqSkomrPmvV27qaU55oiG
|
||||
Cg7w/v7gt7fsMzMczlHfz/Os5wHO2vustfc+57zss/ba4sKFC/D29oabmxvc3Nzg4OAg/1zR
|
||||
@ -156342,8 +156333,8 @@
|
||||
SUVORK5CYII=
|
||||
</office:binary-data>
|
||||
</draw:image>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="13" draw:name="Form10" draw:style-name="gr10" draw:text-style-name="P79" svg:width="4.329cm" svg:height="1.955cm" svg:x="0.288cm" svg:y="0.958cm">
|
||||
<text:p text:style-name="P79">JFXTextfield, hier lässt <text:line-break/>sich ein Pfad zu einer <text:line-break/>Textur eingeben</text:p>
|
||||
</draw:frame><draw:custom-shape text:anchor-type="paragraph" draw:z-index="13" draw:name="Form10" draw:style-name="gr10" draw:text-style-name="P74" svg:width="4.329cm" svg:height="1.955cm" svg:x="0.288cm" svg:y="0.958cm">
|
||||
<text:p text:style-name="P74">JFXTextfield, hier lässt <text:line-break/>sich ein Pfad zu einer <text:line-break/>Textur eingeben</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="22435.8452138493 31630.6582506763" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -156389,8 +156380,8 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="15" draw:name="Form12" draw:style-name="gr12" draw:text-style-name="P80" svg:width="5.462cm" svg:height="1.306cm" svg:x="11.4cm" svg:y="0.702cm">
|
||||
<text:p text:style-name="P79"><text:span text:style-name="T23">JFXButton, öffnet FileChooser</text:span></text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="15" draw:name="Form12" draw:style-name="gr12" draw:text-style-name="P75" svg:width="5.462cm" svg:height="1.306cm" svg:x="11.4cm" svg:y="0.702cm">
|
||||
<text:p text:style-name="P74"><text:span text:style-name="T23">JFXButton, öffnet FileChooser</text:span></text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="857.862447529868 44482.5910931174" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -156436,8 +156427,8 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="16" draw:name="Form13" draw:style-name="gr13" draw:text-style-name="P80" svg:width="4.234cm" svg:height="2.745cm" svg:x="0.24cm" svg:y="4.826cm">
|
||||
<text:p text:style-name="P79"><text:span text:style-name="T23">JFXTextfield, hier wird </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">der Name der </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">ausgeählten textur </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">angezeit </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">und lässt sich ändern</text:span></text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="16" draw:name="Form13" draw:style-name="gr13" draw:text-style-name="P75" svg:width="4.234cm" svg:height="2.745cm" svg:x="0.24cm" svg:y="4.826cm">
|
||||
<text:p text:style-name="P74"><text:span text:style-name="T23">JFXTextfield, hier wird </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">der Name der </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">ausgewählten Textur </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">angezeigt </text:span><text:span text:style-name="T23"><text:line-break/></text:span><text:span text:style-name="T23">und lässt sich ändern</text:span></text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="23435.2353186172 2552.60115606936" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -156483,8 +156474,8 @@
|
||||
<draw:equation draw:name="f41" draw:formula="$1 "/>
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="17" draw:name="Form14" draw:style-name="gr14" draw:text-style-name="P79" svg:width="4.259cm" svg:height="2.936cm" svg:x="12.603cm" svg:y="5.283cm">
|
||||
<text:p text:style-name="P79">JFXChipView, hier <text:line-break/>lassen sich Tags setzen. <text:line-break/>Resolution und Date<text:line-break/>werden automatisch <text:line-break/>gesetzt</text:p>
|
||||
</draw:custom-shape><draw:custom-shape text:anchor-type="paragraph" draw:z-index="17" draw:name="Form14" draw:style-name="gr14" draw:text-style-name="P74" svg:width="4.259cm" svg:height="2.936cm" svg:x="12.603cm" svg:y="5.283cm">
|
||||
<text:p text:style-name="P74">JFXChipView, hier <text:line-break/>lassen sich Tags setzen. <text:line-break/>Resolution und Date<text:line-break/>werden automatisch <text:line-break/>gesetzt</text:p>
|
||||
<draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 0 10800 10800 21600 21600 10800 ?f40 ?f41" draw:text-areas="0 0 21600 21600" draw:type="rectangular-callout" draw:modifiers="-8657.88819875776 10871.3513513514" draw:enhanced-path="M 0 0 L 0 3590 ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010 0 21600 3590 21600 ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600 21600 21600 21600 18010 ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590 21600 0 18010 0 ?f14 ?f15 12630 0 8970 0 ?f16 ?f17 3590 0 0 0 Z N">
|
||||
<draw:equation draw:name="f0" draw:formula="$0 -10800"/>
|
||||
<draw:equation draw:name="f1" draw:formula="$1 -10800"/>
|
||||
@ -156531,8 +156522,8 @@
|
||||
<draw:handle draw:handle-position="$0 $1"/>
|
||||
</draw:enhanced-geometry>
|
||||
</draw:custom-shape></text:p>
|
||||
<text:p text:style-name="P18">Die ImportView wird angezeigt wenn man eine neue Textur importier<text:span text:style-name="T24">t</text:span>. Sie besteht aus einem Textfield in welchem sich der Pfad zur Textur eingeben lässt, wird die Textur über den FileChooser ausgewählt, wird dieser Pfad im Textfield angezeigt. Über ein weiteres Textfield lässt sich ein beliebiger eindeutiger Name setzten, dieser ist am Anfang auf den Name der Textur gesetzt. Tags lassen sich über eine JFXChipView setzen. </text:p>
|
||||
<text:h text:style-name="P36" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc752_3921231312"/>Changelog<text:bookmark-end text:name="__RefHeading___Toc752_3921231312"/></text:h>
|
||||
<text:p text:style-name="P18">Die ImportView wird angezeigt wenn man eine neue Textur importier<text:span text:style-name="T24">t</text:span>. Sie besteht aus einem Textfield, in welchem sich der Pfad zur Textur eingeben lässt. <text:span text:style-name="T26">W</text:span>ird die Textur über den FileChooser ausgewählt, wird dieser Pfad im Textfield angezeigt. Über ein weiteres Textfield lässt sich ein beliebiger eindeutiger Name setzen, dieser ist am Anfang auf den Name der Textur gesetzt. Tags lassen sich über eine JFXChipView setzen. </text:p>
|
||||
<text:h text:style-name="P40" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc752_3921231312"/>Changelog<text:bookmark-end text:name="__RefHeading___Toc752_3921231312"/></text:h>
|
||||
<table:table table:name="Tabelle3" table:style-name="Tabelle3">
|
||||
<table:table-column table:style-name="Tabelle3.A"/>
|
||||
<table:table-column table:style-name="Tabelle3.B"/>
|
||||
@ -156546,26 +156537,34 @@
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:style-name="Tabelle3.A2" office:value-type="string">
|
||||
<text:p text:style-name="P30">1.0.<text:span text:style-name="T21">0</text:span></text:p>
|
||||
<text:p text:style-name="P32">1.0.<text:span text:style-name="T21">0</text:span></text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle3.B2" office:value-type="string">
|
||||
<text:p text:style-name="P30">Dokument <text:span text:style-name="T21">UI Elemente</text:span> erstellt</text:p>
|
||||
<text:p text:style-name="P32">Dokument <text:span text:style-name="T21">UI Elemente</text:span> erstellt</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:style-name="Tabelle3.A3" office:value-type="string">
|
||||
<text:p text:style-name="P34">1.1.0</text:p>
|
||||
<text:p text:style-name="P36">1.1.0</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle3.B3" office:value-type="string">
|
||||
<text:p text:style-name="P34">Detailbeschreibung hinzugefügt</text:p>
|
||||
<text:p text:style-name="P36">Detailbeschreibung hinzugefügt</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:style-name="Tabelle3.A4" office:value-type="string">
|
||||
<text:p text:style-name="P68">1.2.0</text:p>
|
||||
<text:p text:style-name="P37">1.2.0</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle3.B4" office:value-type="string">
|
||||
<text:p text:style-name="P68">Einleitung und Diagramm hinzugefügt</text:p>
|
||||
<text:p text:style-name="P37">Einleitung und Diagramm hinzugefügt</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row>
|
||||
<table:table-cell table:style-name="Tabelle3.A5" office:value-type="string">
|
||||
<text:p text:style-name="P38">1.2.1</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle3.B5" office:value-type="string">
|
||||
<text:p text:style-name="P38"><text:span text:style-name="T27">Exportieren</text:span> mittels Kontextmenü</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
|
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
|
@ -1,24 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta><meta:creation-date>2019-03-26T16:04:36.670567469</meta:creation-date><meta:editing-duration>PT3H26M43S</meta:editing-duration><meta:editing-cycles>93</meta:editing-cycles><meta:generator>LibreOffice/6.2.3.2$Linux_X86_64 LibreOffice_project/20$Build-2</meta:generator><dc:title>Pflichtenheft</dc:title><dc:date>2019-05-02T13:12:23.524684391</dc:date><meta:document-statistic meta:table-count="4" meta:image-count="4" meta:object-count="0" meta:page-count="12" meta:paragraph-count="157" meta:word-count="1291" meta:character-count="9427" meta:non-whitespace-character-count="8307"/><meta:user-defined meta:name="Version">1.1.1</meta:user-defined></office:meta>
|
||||
<office:meta><meta:creation-date>2019-03-26T16:04:36.670567469</meta:creation-date><meta:editing-duration>PT3H31M5S</meta:editing-duration><meta:editing-cycles>97</meta:editing-cycles><meta:generator>LibreOffice/6.2.3.2$Linux_X86_64 LibreOffice_project/20$Build-2</meta:generator><dc:title>Pflichtenheft</dc:title><dc:date>2019-05-10T15:29:54.965397682</dc:date><meta:document-statistic meta:table-count="4" meta:image-count="4" meta:object-count="0" meta:page-count="13" meta:paragraph-count="160" meta:word-count="1320" meta:character-count="9647" meta:non-whitespace-character-count="8502"/><meta:user-defined meta:name="Version">1.1.2</meta:user-defined></office:meta>
|
||||
<office:settings>
|
||||
<config:config-item-set config:name="ooo:view-settings">
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="ViewAreaTop" config:type="long">366183</config:config-item>
|
||||
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="ViewAreaWidth" config:type="long">49108</config:config-item>
|
||||
<config:config-item config:name="ViewAreaHeight" config:type="long">22677</config:config-item>
|
||||
<config:config-item config:name="ViewAreaWidth" config:type="long">50034</config:config-item>
|
||||
<config:config-item config:name="ViewAreaHeight" config:type="long">22650</config:config-item>
|
||||
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
|
||||
<config:config-item-map-indexed config:name="Views">
|
||||
<config:config-item-map-entry>
|
||||
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">32905</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">75895</config:config-item>
|
||||
<config:config-item config:name="ViewLeft" config:type="long">16514</config:config-item>
|
||||
<config:config-item config:name="ViewTop" config:type="long">370782</config:config-item>
|
||||
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
|
||||
<config:config-item config:name="VisibleRight" config:type="long">49107</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">22675</config:config-item>
|
||||
<config:config-item config:name="VisibleTop" config:type="long">366183</config:config-item>
|
||||
<config:config-item config:name="VisibleRight" config:type="long">50033</config:config-item>
|
||||
<config:config-item config:name="VisibleBottom" config:type="long">388832</config:config-item>
|
||||
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item>
|
||||
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
|
||||
@ -105,7 +105,7 @@
|
||||
<config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">4254429</config:config-item>
|
||||
<config:config-item config:name="Rsid" config:type="int">4333506</config:config-item>
|
||||
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
|
||||
<config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
|
||||
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
|
||||
@ -117860,6 +117860,15 @@
|
||||
<style:style style:name="Tabelle4.B4" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Tabelle4.5" style:family="table-row">
|
||||
<style:table-row-properties style:min-row-height="0.762cm"/>
|
||||
</style:style>
|
||||
<style:style style:name="Tabelle4.A5" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="Tabelle4.B5" style:family="table-cell">
|
||||
<style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.05pt solid #000000" fo:border-right="0.05pt solid #000000" fo:border-top="none" fo:border-bottom="0.05pt solid #000000"/>
|
||||
</style:style>
|
||||
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Footer">
|
||||
<style:text-properties officeooo:paragraph-rsid="0010decd"/>
|
||||
</style:style>
|
||||
@ -118078,81 +118087,96 @@
|
||||
<style:style style:name="P65" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="0037289a" officeooo:paragraph-rsid="0037289a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P66" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:paragraph-rsid="0037289a"/>
|
||||
<style:style style:name="P66" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="0039cb23" officeooo:paragraph-rsid="00333ec7"/>
|
||||
</style:style>
|
||||
<style:style style:name="P67" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:rsid="00390786" officeooo:paragraph-rsid="00390786"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="0037289a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P68" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:paragraph-rsid="00390786"/>
|
||||
<style:text-properties officeooo:rsid="00390786" officeooo:paragraph-rsid="00390786"/>
|
||||
</style:style>
|
||||
<style:style style:name="P69" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:rsid="003bb8dc" officeooo:paragraph-rsid="003bb8dc"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="00390786"/>
|
||||
</style:style>
|
||||
<style:style style:name="P70" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:rsid="003bb8dc" officeooo:paragraph-rsid="003bb8dc"/>
|
||||
</style:style>
|
||||
<style:style style:name="P71" style:family="paragraph" style:parent-style-name="Standard">
|
||||
<style:text-properties officeooo:rsid="003bb8dc" officeooo:paragraph-rsid="003f3a75"/>
|
||||
</style:style>
|
||||
<style:style style:name="P71" style:family="paragraph" style:parent-style-name="Text_20_body" style:list-style-name="">
|
||||
<style:style style:name="P72" style:family="paragraph" style:parent-style-name="List_20_Contents">
|
||||
<style:text-properties officeooo:rsid="00420ecb" officeooo:paragraph-rsid="00420ecb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P73" style:family="paragraph" style:parent-style-name="Text_20_body" style:list-style-name="">
|
||||
<style:paragraph-properties>
|
||||
<style:tab-stops>
|
||||
<style:tab-stop style:position="14.203cm"/>
|
||||
</style:tab-stops>
|
||||
</style:paragraph-properties>
|
||||
</style:style>
|
||||
<style:style style:name="P72" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:list-style-name="L2">
|
||||
<style:style style:name="P74" style:family="paragraph" style:parent-style-name="Text_20_body">
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="00421fc2"/>
|
||||
</style:style>
|
||||
<style:style style:name="P75" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:list-style-name="L2">
|
||||
<style:text-properties officeooo:rsid="00351d46" officeooo:paragraph-rsid="0037289a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P73" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P76" style:family="paragraph" style:parent-style-name="Table_20_Contents">
|
||||
<style:text-properties officeooo:rsid="00421fc2" officeooo:paragraph-rsid="00421fc2"/>
|
||||
</style:style>
|
||||
<style:style style:name="P77" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:paragraph-rsid="00085afb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P74" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P78" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:text-properties officeooo:rsid="00328ab8" officeooo:paragraph-rsid="00328ab8"/>
|
||||
</style:style>
|
||||
<style:style style:name="P75" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P79" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="00085afb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P76" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:style style:name="P80" style:family="paragraph" style:parent-style-name="Heading_20_1">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:rsid="00398ec6" officeooo:paragraph-rsid="00398ec6"/>
|
||||
</style:style>
|
||||
<style:style style:name="P77" style:family="paragraph" style:parent-style-name="Title" style:master-page-name="First_20_Page">
|
||||
<style:style style:name="P81" style:family="paragraph" style:parent-style-name="Title" style:master-page-name="First_20_Page">
|
||||
<style:paragraph-properties style:page-number="auto"/>
|
||||
<style:text-properties officeooo:paragraph-rsid="0004b30c"/>
|
||||
</style:style>
|
||||
<style:style style:name="P78" style:family="paragraph" style:parent-style-name="List_20_Contents" style:list-style-name="L1"/>
|
||||
<style:style style:name="P79" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P82" style:family="paragraph" style:parent-style-name="List_20_Contents" style:list-style-name="L1"/>
|
||||
<style:style style:name="P83" style:family="paragraph" style:parent-style-name="List_20_Contents" style:list-style-name="L1">
|
||||
<style:text-properties officeooo:rsid="00420ecb" officeooo:paragraph-rsid="00420ecb"/>
|
||||
</style:style>
|
||||
<style:style style:name="P84" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:rsid="00328ab8" officeooo:paragraph-rsid="00328ab8"/>
|
||||
</style:style>
|
||||
<style:style style:name="P80" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P85" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="00333ec7"/>
|
||||
</style:style>
|
||||
<style:style style:name="P81" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:style style:name="P86" style:family="paragraph" style:parent-style-name="Heading_20_2">
|
||||
<style:text-properties officeooo:paragraph-rsid="003d80d6"/>
|
||||
</style:style>
|
||||
<style:style style:name="P82" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P87" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="00328ab8" officeooo:paragraph-rsid="00328ab8"/>
|
||||
</style:style>
|
||||
<style:style style:name="P83" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P88" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="00333ec7"/>
|
||||
</style:style>
|
||||
<style:style style:name="P84" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P89" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="00361ba9"/>
|
||||
</style:style>
|
||||
<style:style style:name="P85" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P90" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="003f3a75"/>
|
||||
</style:style>
|
||||
<style:style style:name="P86" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P91" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="0039cb23"/>
|
||||
</style:style>
|
||||
<style:style style:name="P87" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P92" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="0039cb23" officeooo:paragraph-rsid="00333ec7"/>
|
||||
</style:style>
|
||||
<style:style style:name="P88" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P93" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:text-properties officeooo:rsid="0037289a" officeooo:paragraph-rsid="0037289a"/>
|
||||
</style:style>
|
||||
<style:style style:name="P89" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:style style:name="P94" style:family="paragraph" style:parent-style-name="Heading_20_3">
|
||||
<style:paragraph-properties fo:break-before="page"/>
|
||||
<style:text-properties officeooo:rsid="00333ec7" officeooo:paragraph-rsid="00333ec7"/>
|
||||
</style:style>
|
||||
@ -118240,6 +118264,9 @@
|
||||
<style:style style:name="T28" style:family="text">
|
||||
<style:text-properties officeooo:rsid="003f3a75"/>
|
||||
</style:style>
|
||||
<style:style style:name="T29" style:family="text">
|
||||
<style:text-properties officeooo:rsid="00421fc2"/>
|
||||
</style:style>
|
||||
<style:style style:name="fr1" style:family="graphic" style:parent-style-name="Graphics">
|
||||
<style:graphic-properties style:vertical-pos="top" style:vertical-rel="paragraph" style:horizontal-pos="center" style:horizontal-rel="paragraph" style:mirror="none" fo:clip="rect(0cm, 0cm, 0cm, 0cm)" draw:luminance="0%" draw:contrast="0%" draw:red="0%" draw:green="0%" draw:blue="0%" draw:gamma="100%" draw:color-inversion="false" draw:image-opacity="100%" draw:color-mode="standard"/>
|
||||
</style:style>
|
||||
@ -118399,7 +118426,7 @@
|
||||
<office:master-styles>
|
||||
<style:master-page style:name="Standard" style:page-layout-name="pm1">
|
||||
<style:footer>
|
||||
<text:p text:style-name="P1"><text:span text:style-name="T1">TextureSync</text:span> <text:tab/><text:title>Pflichtenheft</text:title> <text:span text:style-name="T2">⸬ </text:span><text:span text:style-name="T1">Version </text:span><text:user-defined style:data-style-name="N0" text:name="Version">1.1.1</text:user-defined><text:tab/><text:span text:style-name="T3">Seite</text:span> <text:page-number text:select-page="current">12</text:page-number>/<text:page-count>12</text:page-count></text:p>
|
||||
<text:p text:style-name="P1"><text:span text:style-name="T1">TextureSync</text:span> <text:tab/><text:title>Pflichtenheft</text:title> <text:span text:style-name="T2">⸬ </text:span><text:span text:style-name="T1">Version </text:span><text:user-defined style:data-style-name="N0" text:name="Version">1.1.2</text:user-defined><text:tab/><text:span text:style-name="T3">Seite</text:span> <text:page-number text:select-page="current">12</text:page-number>/<text:page-count>13</text:page-count></text:p>
|
||||
</style:footer>
|
||||
</style:master-page>
|
||||
<style:master-page style:name="First_20_Page" style:display-name="First Page" style:page-layout-name="pm2" style:next-style-name="Standard"/>
|
||||
@ -118416,7 +118443,7 @@
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
|
||||
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
|
||||
</text:sequence-decls>
|
||||
<text:p text:style-name="P77"/>
|
||||
<text:p text:style-name="P81"/>
|
||||
<text:p text:style-name="P42"/>
|
||||
<text:p text:style-name="Title"><text:title>Pflichtenheft</text:title></text:p>
|
||||
<text:p text:style-name="Subtitle">TextureSync</text:p>
|
||||
@ -118441,7 +118468,7 @@
|
||||
<text:p text:style-name="P24">Version</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle1.B1" office:value-type="string">
|
||||
<text:p text:style-name="P23"><text:user-defined style:data-style-name="N0" text:name="Version">1.1.1</text:user-defined></text:p>
|
||||
<text:p text:style-name="P23"><text:user-defined style:data-style-name="N0" text:name="Version">1.1.2</text:user-defined></text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Tabelle1.1">
|
||||
@ -118472,7 +118499,7 @@
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
<text:h text:style-name="P71" text:outline-level="1"/>
|
||||
<text:h text:style-name="P73" text:outline-level="1"/>
|
||||
<text:table-of-content text:style-name="Sect1" text:protected="true" text:name="Inhaltsverzeichnis1">
|
||||
<text:table-of-content-source text:outline-level="2">
|
||||
<text:index-title-template text:style-name="Contents_20_Heading">Inhaltsverzeichnis</text:index-title-template>
|
||||
@ -118602,22 +118629,25 @@
|
||||
<text:h text:style-name="P61" text:outline-level="3"/>
|
||||
<text:h text:style-name="P39" text:outline-level="3"><text:span text:style-name="T25">MK#5 </text:span>Filter</text:h>
|
||||
<text:p text:style-name="P9">Der Nutzer kann die am Client angezeigten Texturen nach verschiedenen Kriterien filtern. Mögliche Kriterien sind:</text:p>
|
||||
<text:list xml:id="list3224168282" text:style-name="L1">
|
||||
<text:list xml:id="list2604854953" text:style-name="L1">
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P78">Das Vorhandensein von Tags</text:p>
|
||||
<text:p text:style-name="P82">Das Vorhandensein von Tags</text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P78">Das nicht-Vorhandensein von Tags</text:p>
|
||||
<text:p text:style-name="P82">Das nicht-Vorhandensein von Tags</text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P78">Mindestauflösung</text:p>
|
||||
<text:p text:style-name="P82">Mindestauflösung</text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P78">Maximalauflösung</text:p>
|
||||
<text:p text:style-name="P82">Maximalauflösung</text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P78">Stichworte, die im Namen der Textur vorhanden sind</text:p>
|
||||
<text:p text:style-name="P78"/>
|
||||
<text:p text:style-name="P82">Stichworte, die im Namen der Textur vorhanden sind</text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P83">Einfügedatum</text:p>
|
||||
<text:p text:style-name="P82"/>
|
||||
</text:list-item>
|
||||
</text:list>
|
||||
<text:p text:style-name="P9">Beliebige Kombinationen dieser Kriterien sind möglich, um nur die Texturen anzuzeigen, die alle eingestellten Kriterien erfüllen.</text:p>
|
||||
@ -118625,12 +118655,12 @@
|
||||
<text:p text:style-name="P9">Alle gefundenen Ergebnisse werden jeweils mit einer 2D-Vorschau aufgelistet.</text:p>
|
||||
<text:h text:style-name="P61" text:outline-level="3"><text:span text:style-name="T25">MK#6 </text:span>Synchronisation</text:h>
|
||||
<text:p text:style-name="P12">Hat ein Nutzer eine Textur erfolgreich zur Sammlung hinzugefügt oder Tags zu einer Textur hinzugefügt bzw. entfernt, <text:span text:style-name="T18">ist</text:span> diese <text:span text:style-name="T18">geänderte </text:span>Textur sowie deren <text:span text:style-name="T19">Metadaten</text:span> für alle anderen Nutzer sichtbar.</text:p>
|
||||
<text:h text:style-name="P87" text:outline-level="3">MK#7 Import</text:h>
|
||||
<text:h text:style-name="P66" text:outline-level="3">MK#7 Import</text:h>
|
||||
<text:p text:style-name="P14">Jede gewünschte Textur lässt sich aus lokalen Dateisystem <text:span text:style-name="T28">importieren</text:span> und <text:span text:style-name="T28">mit Metadaten </text:span>der Sammlung <text:span text:style-name="T28">hinzuzufügen</text:span>.</text:p>
|
||||
<text:h text:style-name="P64" text:outline-level="3"><text:span text:style-name="T25">MK#8 </text:span>Export</text:h>
|
||||
<text:p text:style-name="P14">Jede gewünschte Textur lässt sich aus der Sammlung exportieren und im lokalen Dateisystem des Clients an einem beliebigen Ort abspeichern.</text:p>
|
||||
<text:p text:style-name="P14">Jede gewünschte Textur lässt sich aus der Sammlung exportieren und im lokalen Dateisystem des Clients an einem beliebigen Ort abspeichern. <text:span text:style-name="T29">Beim fehlschlagen des Exports wird eine Fehlermeldung dem Nutzer angezeigt.</text:span></text:p>
|
||||
<text:h text:style-name="P61" text:outline-level="3"><text:span text:style-name="T25">MK#9 </text:span>Atomarer Upload</text:h>
|
||||
<text:p text:style-name="P9">Eine neue Textur wird erst dann in die Sammlung übernommen, wenn der Upload auf den Server vollständig und erfolgreich war. Sollte ein Upload durch einen Netzwerkausfall abbrechen, wird keine defekte Textur in die Sammlung aufgenommen.</text:p>
|
||||
<text:p text:style-name="P74">Eine neue Textur wird erst dann in die Sammlung übernommen, wenn der Upload auf den Server vollständig und erfolgreich war. Sollte ein Upload durch einen Netzwerkausfall abbrechen, wird keine defekte Textur in die Sammlung aufgenommen. <text:span text:style-name="T29">Beim fehlschlagen des Uploads wird eine Fehlermeldung dem Nutzer angezeigt.</text:span></text:p>
|
||||
<text:h text:style-name="P63" text:outline-level="3"><text:span text:style-name="T25">MK#10 </text:span>3D-Ansicht</text:h>
|
||||
<text:p text:style-name="P13">Der Nutzer könnte bei Auswahl einer Textur eine 3D-Vorschau angezeigt bekommen um eine räumliche Vorstellung davon zu erhalten.</text:p>
|
||||
<text:h text:style-name="P51" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc1974_1634689507"/><text:soft-page-break/>Wunschkriterien<text:bookmark-end text:name="__RefHeading___Toc1974_1634689507"/></text:h>
|
||||
@ -118722,15 +118752,15 @@
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle2.B2" office:value-type="string">
|
||||
<text:p text:style-name="P30">Frühzeitig Proof-of-Concept/Prototypen erstellen. Alternativen:</text:p>
|
||||
<text:list xml:id="list111972934" text:style-name="L2">
|
||||
<text:list xml:id="list940817753" text:style-name="L2">
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P72">Server erstellt Preview </text:p>
|
||||
<text:p text:style-name="P75">Server erstellt Preview </text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P72">Wechsel <text:span text:style-name="T20">des </text:span>UI-Framework<text:span text:style-name="T20">s.</text:span></text:p>
|
||||
<text:p text:style-name="P75">Wechsel <text:span text:style-name="T20">des </text:span>UI-Framework<text:span text:style-name="T20">s.</text:span></text:p>
|
||||
</text:list-item>
|
||||
<text:list-item>
|
||||
<text:p text:style-name="P72">Nur 2D-Preview</text:p>
|
||||
<text:p text:style-name="P75">Nur 2D-Preview</text:p>
|
||||
</text:list-item>
|
||||
</text:list>
|
||||
</table:table-cell>
|
||||
@ -118825,7 +118855,7 @@
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
<text:p text:style-name="P68"/>
|
||||
<text:p text:style-name="P69"/>
|
||||
<text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark-start text:name="__RefHeading___Toc641_369916577"/><text:soft-page-break/>Mockups<text:bookmark-end text:name="__RefHeading___Toc641_369916577"/></text:h>
|
||||
<text:h text:style-name="Heading_20_2" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc643_369916577"/>Erster Start<text:bookmark-end text:name="__RefHeading___Toc643_369916577"/></text:h>
|
||||
<text:p text:style-name="P21"><draw:frame draw:style-name="fr1" draw:name="Bild1" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="0"><draw:image loext:mime-type="image/png">
|
||||
@ -120479,8 +120509,8 @@
|
||||
</draw:image>
|
||||
</draw:frame>Das Hauptfenster besteht aus einer Pane für die Anzeige der Texturen und einer Detailansicht. Die Detailansicht beinhaltet eine 3D-Preview, eine ChipView zur Darstellung der Tags, sowie die zur Textur gehörenden Metadaten. <text:span text:style-name="T27">Über die ChipView lassen sich Tags bearbeiten und hinzufügen</text:span>. <text:span text:style-name="T27">Der</text:span> Butt<text:span text:style-name="T27">o</text:span>n rechts unten <text:span text:style-name="T27">öffnet</text:span> das Textur importieren Fenster.</text:p>
|
||||
<text:p text:style-name="P21"/>
|
||||
<text:h text:style-name="P57" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc1145_3853282256"/>Dialog<text:bookmark-end text:name="__RefHeading___Toc1145_3853282256"/></text:h>
|
||||
<text:p text:style-name="P70"><draw:frame draw:style-name="fr2" draw:name="Bild3" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.802cm" draw:z-index="3"><draw:image loext:mime-type="image/png">
|
||||
<text:h text:style-name="P57" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc1145_3853282256"/><text:soft-page-break/>Dialog<text:bookmark-end text:name="__RefHeading___Toc1145_3853282256"/></text:h>
|
||||
<text:p text:style-name="P71"><draw:frame draw:style-name="fr2" draw:name="Bild3" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.802cm" draw:z-index="3"><draw:image loext:mime-type="image/png">
|
||||
<office:binary-data>iVBORw0KGgoAAAANSUhEUgAABIUAAAKbCAYAAABrQDp8AAAgAElEQVR4nOzde5hbVb3w8bQF
|
||||
W6BQtOAj6gMeq+mFMpRepjBtpzLTmcxkppOZzDWZmWQmmQRBON4O6MHL6+ODHLxwUI9aL+hR
|
||||
vMtR9BzEI94QRUXwcMALgoigFkQUUBGOBfp7/6g7JJmdZO+dvbJWsr/reT7PA512kmlnlb2+
|
||||
@ -121537,11 +121567,11 @@
|
||||
TkSuQmCC
|
||||
</office:binary-data>
|
||||
</draw:image>
|
||||
</draw:frame><text:soft-page-break/>Texturen lassen sich über eine<text:span text:style-name="T28">n Button löschen wenn diese ausgewählte ist. Über eine Doppelbestätigung wird verhindert das eine Textur versehentlich gelöscht wird. </text:span></text:p>
|
||||
</draw:frame>Texturen lassen sich über eine<text:span text:style-name="T28">n Button löschen wenn diese ausgewählte ist. Über eine Doppelbestätigung wird verhindert das eine Textur versehentlich gelöscht wird. </text:span></text:p>
|
||||
<text:p text:style-name="P22"/>
|
||||
<text:p text:style-name="P22"/>
|
||||
<text:h text:style-name="Heading_20_2" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc649_369916577"/>Textur importieren<text:bookmark-end text:name="__RefHeading___Toc649_369916577"/></text:h>
|
||||
<text:p text:style-name="P69"><draw:frame draw:style-name="fr2" draw:name="Bild4" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="2"><draw:image loext:mime-type="image/png">
|
||||
<text:h text:style-name="Heading_20_2" text:outline-level="2"><text:bookmark-start text:name="__RefHeading___Toc649_369916577"/><text:soft-page-break/>Textur importieren<text:bookmark-end text:name="__RefHeading___Toc649_369916577"/></text:h>
|
||||
<text:p text:style-name="P70"><draw:frame draw:style-name="fr2" draw:name="Bild4" text:anchor-type="paragraph" svg:width="17.006cm" svg:height="9.045cm" draw:z-index="2"><draw:image loext:mime-type="image/png">
|
||||
<office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAvAAAAGQCAYAAADIulS9AAAgAElEQVR4nOzdeVhV1eI+8MWM
|
||||
zAioIII5oowKioAKkkqO4DwWlkNqGta362w4p+kNNUstNfNWzmCOqSkomrPmvV27qaU55oiG
|
||||
Cg7w/v7gt7fsMzMczlHfz/Os5wHO2vustfc+57zss/ba4sKFC/D29oabmxvc3Nzg4OAg/1zR
|
||||
@ -122038,6 +122068,14 @@
|
||||
<text:p text:style-name="P35">Import als Kriterium hinzuzufügen; Beschreibung Delete-View </text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
<table:table-row table:style-name="Tabelle4.5">
|
||||
<table:table-cell table:style-name="Tabelle4.A5" office:value-type="string">
|
||||
<text:p text:style-name="P76">1.1.2</text:p>
|
||||
</table:table-cell>
|
||||
<table:table-cell table:style-name="Tabelle4.B5" office:value-type="string">
|
||||
<text:p text:style-name="P76">Einfügedatum in MK#5 und Fehlermeldung hinzugefügt</text:p>
|
||||
</table:table-cell>
|
||||
</table:table-row>
|
||||
</table:table>
|
||||
<text:p text:style-name="P20"/>
|
||||
</office:text>
|
||||
|
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)
|
@ -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>
|
||||
@ -69,7 +71,7 @@
|
||||
<predecessor id="1" predecessor-id="8" type="FS"/>
|
||||
</predecessors>
|
||||
</task>
|
||||
<task id="15" name="UI Elemente beschreiben" note="" work="86400" start="20190409T133640Z" end="20190416T133640Z" work-start="20190409T133640Z" percent-complete="10" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="15" name="UI Elemente beschreiben" note="" work="86400" start="20190409T133640Z" end="20190416T133640Z" work-start="20190409T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="8" type="FS"/>
|
||||
</predecessors>
|
||||
@ -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">
|
||||
</task>
|
||||
<task id="21" name="Client" note="" work="1123200" start="20190416T133640Z" end="20190612T170000Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="22" name="UI" note="" work="288000" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="23" name="Previews" note="" work="86400" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="24" name="Filter UI" note="" work="86400" start="20190416T133640Z" end="20190423T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="25" name="3D Visualisierung" note="" work="57600" start="20190416T133640Z" end="20190420T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="26" name="Dateien hinzufügen" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="27" name="Dateien lokal abspeichern" note="" work="28800" start="20190416T133640Z" end="20190417T133640Z" work-start="20190416T133640Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
</task>
|
||||
<task id="28" name="Grund Architektur" note="" work="662400" start="20190423T000000Z" end="20190612T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
<task id="29" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
</task>
|
||||
<task id="22" name="Client" note="" work="547200" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="30" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<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="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="27" name="Dateien hinzufügen" note="" work="28800" start="20190423T170000Z" end="20190424T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
<task id="28" name="Dateien lokal abspeichern" note="" work="28800" start="20190423T170000Z" end="20190424T170000Z" work-start="20190424T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work"/>
|
||||
</task>
|
||||
<task id="29" name="Grund Architektur" note="" work="86400" start="20190423T000000Z" end="20190427T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
<task id="30" name="Protokoll implementieren" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<constraint type="start-no-earlier-than" time="20190423T000000Z"/>
|
||||
</task>
|
||||
</task>
|
||||
<task id="31" name="Server" note="" work="489600" start="20190423T000000Z" end="20190511T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="11" type="FS"/>
|
||||
</predecessors>
|
||||
<task id="32" name="Dateien speichern/verwalten" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="31" name="Dateien speichern/verwalten" note="" work="172800" start="20190423T000000Z" end="20190504T170000Z" work-start="20190423T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<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="5" 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="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="33" name="Suche implementieren" note="" work="86400" start="20190504T170000Z" end="20190511T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="31" 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="0" priority="0" type="normal" scheduling="fixed-work">
|
||||
<task id="34" name="Hauptlogik implementieren" note="Hauptsächlich Glue-Code " work="57600" start="20190504T170000Z" end="20190508T170000Z" work-start="20190507T080000Z" percent-complete="100" priority="0" type="normal" scheduling="fixed-work">
|
||||
<predecessors>
|
||||
<predecessor id="1" predecessor-id="33" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="32" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="31" 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="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="31" type="FS"/>
|
||||
<predecessor id="1" predecessor-id="22" type="FS"/>
|
||||
<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"/>
|
||||
|
BIN
orga/statusberichte/.odt
Normal file
@ -8,44 +8,40 @@ hier der Statusbericht der vergangenen Woche TextureSync.
|
||||
#Vergangene Arbeitswoche
|
||||
|
||||
##Server
|
||||
Lukas Fürderer hat das Persistence Module implementiert.
|
||||
Robin Willmann erstellete das Grundgerüst der kompletten Servers.
|
||||
Das Netzwerkprotrokoll wurde im Server implementiert.
|
||||
* Lukas Fürderer hat das Persistence Module implementiert.
|
||||
* Robin Willmann erstellte das Grundgerüst des kompletten Servers.
|
||||
* Das Netzwerkprotrokoll wurde im Server implementiert.
|
||||
|
||||
##Client
|
||||
Jannik Seiler und Hendrik Schutter erstellten das Grundgerüst des Clients.
|
||||
* Jannik Seiler und Hendrik Schutter erstellten das Grundgerüst des Clients.
|
||||
|
||||
##Feindesign - UI-Elemente
|
||||
Jannik Seiler erstellte das Dokument. Hendrik Schutter ergänzte.
|
||||
* Jannik Seiler erstellte das Dokument. Hendrik Schutter ergänzte.
|
||||
|
||||
##Review alle Design Dokumente auf Grundlage der Rückmeldung vom 01.05.
|
||||
###Beispiele zur Suche
|
||||
###After-Date zur Suche hinzugefügt
|
||||
###Client checkt Query explizit
|
||||
###Suchergbnis bei leerem Query ergänzt
|
||||
###Fehlermeldungen im Pflichtenheft ergänzt
|
||||
###Doppelte Bestätigung beim Löschen im Pflichtenheft beschrieben
|
||||
###Import im Pflichtenheft beschrieben
|
||||
###Mehrfachaktionen gelöscht
|
||||
* Beispiele zur Suche
|
||||
* After-Date zur Suche hinzugefügt
|
||||
* Client checkt Query explizit
|
||||
* Suchergrbnis bei leerem Query ergänzt
|
||||
* Fehlermeldungen im Pflichtenheft ergänzt
|
||||
* Doppelte Bestätigung beim Löschen im Pflichtenheft beschrieben
|
||||
* Import im Pflichtenheft beschrieben
|
||||
* Mehrfachaktionen gelöscht
|
||||
|
||||
##Planung
|
||||
In der Planung des Projektes wurden die erledigten Aufgaben aktuallisiert.
|
||||
In der Planung des Projektes wurden die erledigten Aufgaben aktualisiert.
|
||||
|
||||
#Nächste Arbeitswoche
|
||||
|
||||
##Feindesign fertigstellen (UI Elemente)
|
||||
|
||||
##Milestone End of Design
|
||||
|
||||
##Suche auf dem Server implementieren
|
||||
|
||||
##MainView des Clients implementieren
|
||||
* Feindesign fertigstellen (UI Elemente)
|
||||
* Milestone End of Design
|
||||
* Suche auf dem Server implementieren
|
||||
* MainView des Clients implementieren
|
||||
|
||||
#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
|
||||
* 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.
|
||||
|
||||
|
42
orga/statusberichte/Statusbericht_KW20.md
Normal file
@ -0,0 +1,42 @@
|
||||
|
||||
#Statusbericht KW20 - TextureSync
|
||||
|
||||
Sehr geehrter Herr Nikolaropoulos,
|
||||
|
||||
hier der Statusbericht der vergangenen Woche TextureSync.
|
||||
|
||||
#Vergangene Arbeitswoche
|
||||
|
||||
##Server
|
||||
* Robin Willmann überarbeitete die Locking-Mechanismen.
|
||||
|
||||
##Client
|
||||
* Jannik Seiler designte ein Modell für die Daten des Netzwerk-Controller und der UI.
|
||||
|
||||
##Review alle Design Dokumente auf Grundlage der Review am 10.05
|
||||
* Feindesign der UI-Elemnte um das Kontextmenü für das Exportieren der Bilder ergänzt.
|
||||
* Das Lastenheft wurde um eine Funktionalität ergänzt.
|
||||
* Im Pflichtenheft wurden das Einfügedatum in MK#5 und die Fehlermeldungen hinzugefügt.
|
||||
|
||||
##Planung
|
||||
In der Planung des Projektes wurden die erledigten Aufgaben aktualisiert.
|
||||
|
||||
#Nächste Arbeitswoche
|
||||
* Server Test vervollständigen
|
||||
* Client Netzwerkschnittstelle
|
||||
|
||||
#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
|
||||
|
||||
|
||||
|
||||
|
27
orga/statusberichte/Statusbericht_KW21.md
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
#Statusbericht KW21 - TextureSync
|
||||
|
||||
Sehr geehrter Herr Nikolaropoulos,
|
||||
|
||||
hier der Statusbericht der vergangenen Woche TextureSync.
|
||||
|
||||
#Vergangene Arbeitswoche
|
||||
|
||||
Robin Willmann ergänzte den Server um einen Logger für Events.
|
||||
|
||||
#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.
|
||||
|
||||
Für kommenden Montag haben wir für das Discord-Treffen einen Server erstellt: https://discord.gg/5Bf4Gza
|
||||
|
||||
Mit freundlichen Grüßen
|
||||
|
||||
Hendrik Schutter und Team TextureSync
|
||||
|
||||
|
||||
|
||||
|
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,11 +1,13 @@
|
||||
[package]
|
||||
name = "texture-sync-server"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
authors = ["CodeSteak <codesteak@shellf.art>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
image = "0.21.1"
|
||||
image = { version = "0.21.1", default-features = false, features = ["jpeg", "png_codec" ] }
|
||||
serde = { version = "1.0.90", features = ["derive"] }
|
||||
serde_json = "1.0.39"
|
||||
lovecraft = "0.2.0"
|
||||
sha2 = "0.8.0"
|
||||
lovecraft = "0.2.0"
|
||||
num_cpus = "1.0"
|
@ -1,8 +1,3 @@
|
||||
// TODO: remove on implementation
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
|
||||
@ -11,14 +6,42 @@ extern crate serde_json;
|
||||
extern crate image;
|
||||
extern crate lovecraft;
|
||||
|
||||
extern crate sha2;
|
||||
|
||||
extern crate num_cpus;
|
||||
|
||||
pub mod model;
|
||||
pub mod persistency;
|
||||
pub mod protocol;
|
||||
pub mod search;
|
||||
use protocol::*;
|
||||
|
||||
mod server_state;
|
||||
use server_state::*;
|
||||
|
||||
fn main() {
|
||||
use std::path::*;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
lovecraft::invoke();
|
||||
panic!("Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn");
|
||||
|
||||
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)?;
|
||||
|
||||
let network_conf = ProtocolConfig::default();
|
||||
|
||||
println!(
|
||||
"listening on {} : {}",
|
||||
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(())
|
||||
}
|
||||
|
119
server/texture-sync-server/src/model/date.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct Date {
|
||||
pub year: u16,
|
||||
pub month: u8,
|
||||
pub day: u8,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
#[cfg(test)]
|
||||
pub fn new(year: u16, month: u8, day: u8) -> Self {
|
||||
Date { year, month, day }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum DateParseError {
|
||||
InvalidNumber,
|
||||
MonthOutOfRange,
|
||||
DayOutOfRange,
|
||||
WrongFormat,
|
||||
}
|
||||
|
||||
impl FromStr for Date {
|
||||
type Err = DateParseError;
|
||||
|
||||
fn from_str(input: &str) -> Result<Date, DateParseError> {
|
||||
let mut parts = input.splitn(3, "-");
|
||||
|
||||
let year = parts
|
||||
.next()
|
||||
.ok_or(DateParseError::WrongFormat)?
|
||||
.parse::<u16>()
|
||||
.map_err(|_| DateParseError::InvalidNumber)?;
|
||||
|
||||
let month = parts
|
||||
.next()
|
||||
.ok_or(DateParseError::WrongFormat)?
|
||||
.parse::<u8>()
|
||||
.map_err(|_| DateParseError::InvalidNumber)?;
|
||||
|
||||
let day = parts
|
||||
.next()
|
||||
.ok_or(DateParseError::WrongFormat)?
|
||||
.parse::<u8>()
|
||||
.map_err(|_| DateParseError::InvalidNumber)?;
|
||||
|
||||
if month > 12 {
|
||||
return Err(DateParseError::DayOutOfRange);
|
||||
}
|
||||
|
||||
if day > 31 {
|
||||
return Err(DateParseError::MonthOutOfRange);
|
||||
}
|
||||
|
||||
Ok(Date { year, month, day })
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Date {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let data = (self.year, self.month, self.day);
|
||||
|
||||
data.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Date {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Date, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let data = <(u16, u8, u8)>::deserialize(deserializer)?;
|
||||
|
||||
Ok(Date {
|
||||
year: data.0,
|
||||
month: data.1,
|
||||
day: data.2,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn order() {
|
||||
// Other Test not needed, since the Ord is derived.
|
||||
assert!(Date::new(2019, 10, 10) > Date::new(2018, 10, 10));
|
||||
assert!(Date::new(2018, 11, 10) > Date::new(2018, 10, 10));
|
||||
assert!(Date::new(2018, 10, 11) > Date::new(2018, 10, 10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(Ok(Date::new(2019, 10, 10)), Date::from_str("2019-10-10"));
|
||||
|
||||
assert_eq!(Ok(Date::new(2019, 1, 1)), Date::from_str("2019-1-1"));
|
||||
|
||||
assert!(Date::from_str("2019-1-1-444").is_err());
|
||||
|
||||
assert_eq!(
|
||||
Ok(Date::new(2019, 1, 1)),
|
||||
Date::from_str("2019-0000000000001-00000001")
|
||||
);
|
||||
|
||||
assert!(Date::from_str("XXX-400-400").is_err());
|
||||
|
||||
assert!(Date::from_str("400-400-400").is_err());
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +1,79 @@
|
||||
// TODO: remove on implementation
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
use serde::Serialize;
|
||||
use std::io;
|
||||
|
||||
mod sha256;
|
||||
pub use sha256::Sha256;
|
||||
|
||||
mod date;
|
||||
pub use date::Date;
|
||||
|
||||
mod texture_format;
|
||||
pub use texture_format::TextureFormat;
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Serialize, Deserialize, Debug)]
|
||||
#[derive(Eq, PartialEq, PartialOrd, Ord, Clone, Serialize, Deserialize, Debug, Hash)]
|
||||
pub struct Texture {
|
||||
pub id: String,
|
||||
// Mainly sort by: (Ord is derived.)
|
||||
pub name: String,
|
||||
pub added_on: Date,
|
||||
// other attributes
|
||||
pub id: String,
|
||||
pub tags: Vec<String>,
|
||||
pub resolution: (u64, u64),
|
||||
//
|
||||
pub format: TextureFormat,
|
||||
pub resolution: (usize, usize),
|
||||
pub texture_hash: Sha256,
|
||||
}
|
||||
|
||||
pub enum ReplaceTextureStatus {
|
||||
/// Done.
|
||||
Ok,
|
||||
|
||||
/// Call Again With Texture Binary
|
||||
NeedTextureData(Sha256),
|
||||
}
|
||||
|
||||
pub type ProtocolResult<T> = Result<T, ProtocolError>;
|
||||
pub enum ProtocolError {
|
||||
BadRequest(String),
|
||||
FileNotFound(String),
|
||||
Conflict(String),
|
||||
InternalServerError(std::io::Error),
|
||||
NotImplemented,
|
||||
}
|
||||
|
||||
impl From<io::Error> for ProtocolError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
ProtocolError::FileNotFound("File Not Found!".to_string())
|
||||
} else {
|
||||
ProtocolError::InternalServerError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// just a shorthand
|
||||
fn tex(id: i32, name: &str, tags: &str, added_on: &str, resolution: u64) -> Texture {
|
||||
Texture {
|
||||
id: format!("{}", id), // Id should actaly be a uuid, but for testing this is fine.
|
||||
name: name.to_string(),
|
||||
tags: tags.split(",").map(|s| s.trim().to_string()).collect(),
|
||||
added_on: Date::from_str(added_on).unwrap(),
|
||||
resolution: (resolution, resolution),
|
||||
format: TextureFormat::JPEG,
|
||||
texture_hash: Sha256::from_data(b"Some Hash"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn textures_are_sorted_by_name() {
|
||||
let tex1 = tex(1, "AAAAA", "Z", "2019-05-15", 2);
|
||||
let tex2 = tex(0, "B", "A", "2000-01-11", 1);
|
||||
|
||||
assert!(tex1 < tex2);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq)]
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, Ord, Deserialize, Serialize, Eq, Hash, PartialEq)]
|
||||
pub struct Sha256(#[serde(serialize_with = "as_hex", deserialize_with = "from_hex")] pub [u8; 32]);
|
||||
|
||||
fn hash_to_hex_string(hash: &[u8; 32]) -> String {
|
||||
@ -14,9 +16,48 @@ fn hash_to_hex_string(hash: &[u8; 32]) -> String {
|
||||
}
|
||||
|
||||
impl Sha256 {
|
||||
pub fn create_hex_string(&self) -> String {
|
||||
pub fn as_hex_string(&self) -> String {
|
||||
hash_to_hex_string(&self.0)
|
||||
}
|
||||
|
||||
pub fn from_data(data: &[u8]) -> Self {
|
||||
use sha2::Digest;
|
||||
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.input(data);
|
||||
|
||||
let hash_result = hasher.result();
|
||||
|
||||
let mut hash_arr = [0u8; 32];
|
||||
for i in 0..32 {
|
||||
hash_arr[i] = hash_result[i];
|
||||
}
|
||||
|
||||
Sha256(hash_arr)
|
||||
}
|
||||
|
||||
pub fn from_hex(hex_str: &str) -> Option<Self> {
|
||||
if hex_str.len() != 32 * 2 {
|
||||
return None; // String has wrong length
|
||||
}
|
||||
|
||||
let mut out = [0u8; 32];
|
||||
for (i, byte) in out.iter_mut().enumerate() {
|
||||
let string_index = i * 2;
|
||||
*byte = u8::from_str_radix(&hex_str[string_index..=string_index + 1], 16).ok()?;
|
||||
}
|
||||
|
||||
Some(Sha256(out))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Sha256 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for digit in self.0.iter() {
|
||||
write!(f, "{:02X}", digit)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn as_hex<S>(hash: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
|
||||
@ -32,22 +73,10 @@ where
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
fn hex2bytes(s: &str) -> Option<[u8; 32]> {
|
||||
if s.len() != 32 * 2 {
|
||||
return None; // String has wrong length
|
||||
}
|
||||
|
||||
let mut out = [0u8; 32];
|
||||
for (i, byte) in out.iter_mut().enumerate() {
|
||||
let string_index = i * 2;
|
||||
*byte = u8::from_str_radix(&s[string_index..=string_index + 1], 16).ok()?;
|
||||
}
|
||||
|
||||
return Some(out);
|
||||
}
|
||||
|
||||
String::deserialize(deserializer).and_then(|string| {
|
||||
hex2bytes(&string).ok_or_else(|| Error::custom("Invalid HEX String!".to_string()))
|
||||
Ok(Sha256::from_hex(&string)
|
||||
.ok_or_else(|| Error::custom("Invalid HEX String!".to_string()))?
|
||||
.0)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
pub enum TextureFormat {
|
||||
#[serde(rename = "png")]
|
||||
PNG,
|
||||
@ -8,6 +11,12 @@ pub enum TextureFormat {
|
||||
JPEG,
|
||||
}
|
||||
|
||||
impl TextureFormat {
|
||||
fn variants() -> &'static [TextureFormat] {
|
||||
&[TextureFormat::PNG, TextureFormat::JPEG]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Lol, I thought we would need custom code, like for Sha256, but it works out of the box :D
|
||||
|
@ -1,18 +0,0 @@
|
||||
use crate::model::Texture;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct CollectionFile {
|
||||
textures: Vec<Texture>,
|
||||
}
|
||||
|
||||
pub fn load_collection_file(path: &Path) -> io::Result<Vec<Texture>> {
|
||||
let file = fs::File::open(path)?;
|
||||
let buf_reader = io::BufReader::new(file);
|
||||
|
||||
let collection: CollectionFile = serde_json::from_reader(buf_reader)?;
|
||||
Ok(collection.textures)
|
||||
}
|
@ -1,19 +1,23 @@
|
||||
// TODO: remove on implementation
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
|
||||
use crate::model::*;
|
||||
use ::image::*;
|
||||
|
||||
pub struct ConvertConfig {
|
||||
pub desired_size: (usize, usize),
|
||||
}
|
||||
// TODO: Deside on ::image::FilterType;
|
||||
// TODO: Think about size; Hard Code??
|
||||
|
||||
pub fn generate_preview(
|
||||
input: &[u8],
|
||||
format: TextureFormat,
|
||||
config: &ConvertConfig,
|
||||
) -> ::image::ImageResult<Vec<u8>> {
|
||||
unimplemented!()
|
||||
const RESIZE: u32 = 256;
|
||||
|
||||
pub fn generate_preview(input: &[u8], format: TextureFormat) -> ImageResult<Vec<u8>> {
|
||||
// Yes, this guesses the format :D
|
||||
// Also the resize function takes a maximum size and preservs the ratio.
|
||||
let img =
|
||||
::image::load_from_memory(input)?.resize(RESIZE, RESIZE, ::image::FilterType::Nearest);
|
||||
|
||||
let mut out: Vec<u8> = Vec::new();
|
||||
|
||||
match format {
|
||||
TextureFormat::JPEG => img.write_to(&mut out, ImageOutputFormat::JPEG(85))?,
|
||||
TextureFormat::PNG => img.write_to(&mut out, ImageOutputFormat::PNG)?,
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
50
server/texture-sync-server/src/persistency/metadata_file.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::model::Texture;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::{self, *};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct MetadataFile {
|
||||
pub textures: Vec<Texture>,
|
||||
}
|
||||
|
||||
impl MetadataFile {
|
||||
pub fn load(path: &Path) -> io::Result<Self> {
|
||||
if !path.exists() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
let file = fs::File::open(path)?;
|
||||
let buf_reader = io::BufReader::new(file);
|
||||
|
||||
let collection: Self = serde_json::from_reader(buf_reader)?;
|
||||
|
||||
Ok(collection)
|
||||
}
|
||||
|
||||
pub fn from_iterator<'a, I>(i: I) -> Self
|
||||
where
|
||||
I: Iterator<Item = &'a Texture>,
|
||||
{
|
||||
MetadataFile {
|
||||
textures: i.map(|tex| tex.clone()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(self, path: &Path) -> io::Result<()> {
|
||||
let mut path_tmp = path.to_path_buf();
|
||||
assert!(path_tmp.set_extension("js.tmp"));
|
||||
|
||||
let mut base = path.to_path_buf();
|
||||
base.pop();
|
||||
fs::create_dir_all(&base)?;
|
||||
|
||||
let mut file = fs::File::create(&path_tmp)?;
|
||||
serde_json::to_writer(&mut file, &self)?;
|
||||
file.flush()?;
|
||||
drop(file);
|
||||
|
||||
fs::rename(path_tmp, path)
|
||||
}
|
||||
}
|
@ -1,143 +1,308 @@
|
||||
// TODO: remove on implementation
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::model::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::collections::*;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub use self::search::Query;
|
||||
mod collection_file;
|
||||
mod image_convert;
|
||||
mod search;
|
||||
|
||||
pub type TextureFileResult = Result<Arc<Vec<u8>>, TextureFileError>;
|
||||
pub enum TextureFileError {
|
||||
NotFound,
|
||||
IoError(io::Error),
|
||||
ImageError(::image::ImageError),
|
||||
}
|
||||
mod metadata_file;
|
||||
|
||||
type PreviewCache = Arc<RwLock<HashMap<(TextureFormat, Sha256), Vec<u8>>>>;
|
||||
pub struct DataStore {
|
||||
// private attributes
|
||||
// may change
|
||||
data_dir: PathBuf,
|
||||
texture: Vec<Texture>,
|
||||
preview_cache: HashMap<(TextureFormat, Sha256), Arc<Vec<u8>>>,
|
||||
//data_path: PathBuf,
|
||||
base_dir: PathBuf,
|
||||
|
||||
textures: HashSet<Texture>,
|
||||
|
||||
id_index: HashMap<String, Texture>,
|
||||
name_index: HashMap<String, Texture>,
|
||||
|
||||
preview_cache: PreviewCache,
|
||||
}
|
||||
|
||||
impl DataStore {
|
||||
pub fn new(path: &Path) -> io::Result<DataStore> {
|
||||
let base_path = path.to_path_buf();
|
||||
let mut collection_file_path = base_path.clone();
|
||||
collection_file_path.push("collection.json");
|
||||
|
||||
Ok(DataStore {
|
||||
data_dir: base_path,
|
||||
texture: collection_file::load_collection_file(&collection_file_path)?,
|
||||
preview_cache: HashMap::new(),
|
||||
})
|
||||
fn texture_base_path(&self) -> PathBuf {
|
||||
let mut path = self.base_dir.clone();
|
||||
path.push("textures");
|
||||
path
|
||||
}
|
||||
|
||||
pub fn garbage_collect(&mut self) -> io::Result<()> {
|
||||
unimplemented!()
|
||||
fn texture_file_path(&self, sha: &Sha256) -> PathBuf {
|
||||
let mut path = self.texture_base_path();
|
||||
path.push(sha.as_hex_string());
|
||||
path
|
||||
}
|
||||
|
||||
pub fn query(&mut self, query: &self::search::Query) -> Vec<Texture> {
|
||||
unimplemented!();
|
||||
// calls self::search::search(... )
|
||||
fn index_file_path(&self) -> PathBuf {
|
||||
let mut path = self.base_dir.clone();
|
||||
path.push("collection.json");
|
||||
path
|
||||
}
|
||||
|
||||
fn light_clone(&self) -> DataStore {
|
||||
DataStore {
|
||||
base_dir: self.base_dir.clone(),
|
||||
|
||||
textures: Default::default(),
|
||||
id_index: Default::default(),
|
||||
name_index: Default::default(),
|
||||
|
||||
preview_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(base_dir: &Path) -> io::Result<DataStore> {
|
||||
let mut store = DataStore {
|
||||
base_dir: base_dir.into(),
|
||||
|
||||
textures: Default::default(),
|
||||
id_index: Default::default(),
|
||||
name_index: Default::default(),
|
||||
|
||||
preview_cache: Default::default(),
|
||||
};
|
||||
|
||||
let metadata_file = metadata_file::MetadataFile::load(&store.index_file_path())?;
|
||||
|
||||
let mut preview_cache_tasks: Vec<(TextureFormat, Sha256)> = vec![];
|
||||
|
||||
for texture in metadata_file.textures.iter() {
|
||||
preview_cache_tasks.push((TextureFormat::JPEG, texture.texture_hash.clone()));
|
||||
|
||||
match store.insert(texture.clone()) {
|
||||
true => (),
|
||||
false => {
|
||||
panic!("inserting {:#?} failed !!!", texture); // TODO: What should be done?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.garbage_collect()?;
|
||||
store.flush_metadata()?;
|
||||
|
||||
// Background Preview Generation
|
||||
{
|
||||
let number_of_tasks = preview_cache_tasks.len();
|
||||
let preview_cache_tasks = Arc::new(Mutex::new(preview_cache_tasks));
|
||||
|
||||
println!("Generating Previews");
|
||||
for _ in 0..(num_cpus::get() * 2) {
|
||||
let cache = store.preview_cache.clone();
|
||||
let light_copy = store.light_clone();
|
||||
let preview_cache_tasks = preview_cache_tasks.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
while let (Some((format, hash)), n) = {
|
||||
let mut tasks = preview_cache_tasks.lock().unwrap();
|
||||
(tasks.pop(), tasks.len())
|
||||
} {
|
||||
if n % 100 == 0 || n == number_of_tasks - 1 {
|
||||
println!(
|
||||
"[Background] {:5}/{:5} Previews left to generate.",
|
||||
n, number_of_tasks
|
||||
)
|
||||
}
|
||||
|
||||
match light_copy.calculate_texture_preview(&hash, format) {
|
||||
Ok(preview) => {
|
||||
cache.write().unwrap().insert((format, hash), preview);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[Background] Error, Skipped Generated Preview '{}' {:?} \n\t {:?}", hash, format, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
pub fn texture_by_id(&self, id: &str) -> Option<Texture> {
|
||||
self.id_index.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn texture_by_name(&self, id: &str) -> Option<Texture> {
|
||||
self.name_index.get(id).cloned()
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, texture: Texture) -> bool {
|
||||
if self.id_index.contains_key(&texture.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.name_index.contains_key(&texture.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.is_texture_file_on_disk(&texture.texture_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.id_index.insert(texture.id.clone(), texture.clone());
|
||||
self.name_index
|
||||
.insert(texture.name.clone(), texture.clone());
|
||||
self.textures.insert(texture.clone());
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// returns true if successful
|
||||
pub fn delete(&mut self, tex: &Texture) -> bool {
|
||||
// Find the texture
|
||||
let pos = self.texture.iter().position(|e| e == tex);
|
||||
match pos {
|
||||
// Remove it
|
||||
Some(idx) => {
|
||||
let removed = self.texture.remove(idx);
|
||||
let key = (removed.format, removed.texture_hash);
|
||||
self.preview_cache.remove(&key);
|
||||
true
|
||||
}
|
||||
// Texture not found
|
||||
None => false,
|
||||
if self.textures.remove(tex) {
|
||||
// remove
|
||||
assert!(self.id_index.remove(&tex.id).is_some());
|
||||
assert!(self.name_index.remove(&tex.name).is_some());
|
||||
|
||||
// don't delete cache, since it could be used
|
||||
// by other texture.
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, tex: Texture) -> bool {
|
||||
// Check for collisions
|
||||
let pos = self
|
||||
.texture
|
||||
.iter()
|
||||
.position(|e| e.id == tex.id || e.name == tex.name);
|
||||
match pos {
|
||||
// Texture with same name or id is already stored
|
||||
Some(_) => false,
|
||||
// Insert it
|
||||
None => {
|
||||
self.texture.push(tex);
|
||||
true
|
||||
}
|
||||
}
|
||||
/// Check if the texture, given by hash, physically exists on the file system.
|
||||
pub fn is_texture_file_on_disk(&self, hash: &Sha256) -> bool {
|
||||
let file_path = self.texture_file_path(&hash);
|
||||
|
||||
file_path.is_file()
|
||||
}
|
||||
|
||||
pub fn by_name<'a>(&'a self, name: &str) -> Option<&'a Texture> {
|
||||
let pos = self.texture.iter().position(|e| e.name == name);
|
||||
match pos {
|
||||
Some(idx) => Some(self.texture.get(idx).unwrap()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn read_texture_file_by_hash(&self, hash: &Sha256) -> io::Result<Vec<u8>> {
|
||||
use std::fs::*;
|
||||
use std::io::*;
|
||||
|
||||
pub fn by_id<'a, 'b>(&'a self, id: &'b str) -> Option<&'a Texture> {
|
||||
let pos = self.texture.iter().position(|e| e.id == id);
|
||||
match pos {
|
||||
Some(idx) => Some(self.texture.get(idx).unwrap()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
let file_path = self.texture_file_path(&hash);
|
||||
|
||||
pub fn has_hash(&self, hash: &Sha256) -> bool {
|
||||
// TODO What is the purpose of this function?
|
||||
// Currently checks for textures in the list with the given hash.
|
||||
// Should it check for an existing image file instead?
|
||||
self.texture
|
||||
.iter()
|
||||
.position(|e| &e.texture_hash == hash)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn get_texture_file(&mut self, hash: &Sha256) -> TextureFileResult {
|
||||
use io::Read;
|
||||
|
||||
let mut file_path = self.data_dir.clone();
|
||||
file_path.push("textures");
|
||||
file_path.push(hash.create_hex_string().to_lowercase());
|
||||
let mut file = match fs::File::open(file_path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::NotFound {
|
||||
return Err(TextureFileError::NotFound);
|
||||
} else {
|
||||
return Err(TextureFileError::IoError(e));
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut file = File::open(file_path)?;
|
||||
let mut buffer = Vec::new();
|
||||
match file.read_to_end(&mut buffer) {
|
||||
Ok(_) => (),
|
||||
Err(e) => return Err(TextureFileError::IoError(e)),
|
||||
};
|
||||
Ok(Arc::new(buffer))
|
||||
file.read_to_end(&mut buffer)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn get_texture_preview(&mut self, hash: &Sha256) -> TextureFileResult {
|
||||
unimplemented!();
|
||||
pub fn store_texture_file(&mut self, data: &[u8]) -> io::Result<()> {
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
||||
let hash = crate::model::Sha256::from_data(data);
|
||||
|
||||
fs::create_dir_all(&self.texture_base_path())?;
|
||||
let file_path = self.texture_file_path(&hash);
|
||||
|
||||
let mut file = fs::File::create(&file_path)?;
|
||||
|
||||
file.write_all(data)
|
||||
}
|
||||
|
||||
pub fn flush_metadata(&self) -> io::Result<()> {
|
||||
let f = metadata_file::MetadataFile::from_iterator(self.textures.iter());
|
||||
|
||||
f.store(self.index_file_path().as_path())
|
||||
}
|
||||
|
||||
pub fn calculate_texture_preview(
|
||||
&self,
|
||||
hash: &Sha256,
|
||||
desired_format: TextureFormat,
|
||||
) -> io::Result<Vec<u8>> {
|
||||
let original = self.read_texture_file_by_hash(&hash)?;
|
||||
|
||||
let mut preview =
|
||||
image_convert::generate_preview(&original[..], desired_format).map_err(|_e| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "Invalid Texture Image on Disk.")
|
||||
})?;
|
||||
|
||||
preview.shrink_to_fit();
|
||||
|
||||
Ok(preview)
|
||||
}
|
||||
|
||||
pub fn get_texture_preview(
|
||||
&mut self,
|
||||
hash: &Sha256,
|
||||
desired_format: TextureFormat,
|
||||
) -> io::Result<Vec<u8>> {
|
||||
let key = (desired_format.clone(), hash.clone());
|
||||
|
||||
if let Some(preview) = self.preview_cache.read().unwrap().get(&key) {
|
||||
return Ok(preview.clone());
|
||||
}
|
||||
|
||||
let preview = self.calculate_texture_preview(hash, desired_format)?;
|
||||
|
||||
self.preview_cache
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, preview.clone());
|
||||
|
||||
Ok(preview)
|
||||
}
|
||||
|
||||
pub fn borrow_textures(&self) -> impl Iterator<Item = &Texture> {
|
||||
self.textures.iter()
|
||||
}
|
||||
|
||||
pub fn garbage_collect(&mut self) -> io::Result<()> {
|
||||
fn extract_hash(filename: &std::ffi::OsStr) -> Option<Sha256> {
|
||||
// directly return None for invalidly encoded file names
|
||||
let str_name = filename.to_str()?;
|
||||
let hash = Sha256::from_hex(str_name)?;
|
||||
|
||||
// check back to ignore names with lowercase letters
|
||||
if hash.as_hex_string() == str_name {
|
||||
Some(hash)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
let texture_base_path = self.texture_base_path();
|
||||
|
||||
if !texture_base_path.exists() {
|
||||
println!("No Textures Found: Skip GC.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let texture_dir = std::fs::read_dir(texture_base_path)?;
|
||||
|
||||
let mut hashs_on_disk = HashSet::new();
|
||||
for result_direntry in texture_dir {
|
||||
let result_direntry = result_direntry?;
|
||||
|
||||
// Skip if not a file.
|
||||
if !result_direntry.file_type()?.is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let texture_path = result_direntry.path();
|
||||
|
||||
let filename = match texture_path.file_name() {
|
||||
Some(name) => name,
|
||||
None => continue,
|
||||
};
|
||||
match extract_hash(filename) {
|
||||
Some(hash) => {
|
||||
hashs_on_disk.insert(hash);
|
||||
}
|
||||
None => (), // ignore other files
|
||||
};
|
||||
}
|
||||
|
||||
let mut unused_files = hashs_on_disk;
|
||||
for texture in &self.textures {
|
||||
unused_files.remove(&texture.texture_hash);
|
||||
}
|
||||
|
||||
// remove what is still contained in the HashSet
|
||||
for entry in unused_files {
|
||||
let path = self.texture_file_path(&entry);
|
||||
std::fs::remove_file(path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
// TODO: remove on implementation
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
|
||||
use crate::model::*;
|
||||
|
||||
pub struct Query {
|
||||
filters: Vec<QueryFilterModifier>,
|
||||
}
|
||||
|
||||
pub type QueryParserResult = Result<Query, QuerySyntaxError>;
|
||||
pub enum QuerySyntaxError {
|
||||
UnknownFilter,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn parse(input: &[String]) -> QueryParserResult {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(input: &[Texture], query: &Query) -> Vec<Texture> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
enum QueryFilterModifier {
|
||||
None(QueryFilter),
|
||||
Not(QueryFilter),
|
||||
}
|
||||
|
||||
enum QueryFilter {
|
||||
TagName(String),
|
||||
InName(String),
|
||||
MinResolution(usize),
|
||||
BeforeDate { year: u16, month: u16, day: u16 },
|
||||
}
|
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(())
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
use crate::model::*;
|
||||
|
||||
pub enum ReplaceTextureStatus {
|
||||
// Call Again With Texture Binary
|
||||
NeedTextureData(Sha256),
|
||||
// Done.
|
||||
Ok,
|
||||
}
|
||||
|
||||
pub type ProtocolResult<T> = Result<T, ProtocolError>;
|
||||
pub enum ProtocolError {
|
||||
BadRequest(String),
|
||||
FileNotFound(String),
|
||||
Conflict(String),
|
||||
InternalServerError(std::io::Error),
|
||||
NotImplemented,
|
||||
}
|
@ -1,15 +1,24 @@
|
||||
use super::*;
|
||||
|
||||
use std::fmt;
|
||||
use std::io::*;
|
||||
use std::net::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Serialize;
|
||||
|
||||
pub struct Connection<R: Read + Sized, W: Write + Sized> {
|
||||
reader: R,
|
||||
writer: W,
|
||||
}
|
||||
|
||||
impl<R: Read + Sized, W: Write + Sized> fmt::Display for Connection<R, W> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<Client>") // Todo use some form of marker??
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Sized, W: Write + Sized> ClientConnection for Connection<R, W> {}
|
||||
|
||||
const KIB: u32 = 1024;
|
||||
const MIB: u32 = 1024 * 1024;
|
||||
|
||||
@ -139,9 +148,9 @@ impl<R: Read + Sized, W: Write + Sized> Connection<R, W> {
|
||||
_ => (), // else try other
|
||||
}
|
||||
|
||||
let json: Command = serde_json::from_slice(&payload[..]).map_err(|e| {
|
||||
let json: Command = serde_json::from_slice(&payload[..]).map_err(|_e| {
|
||||
#[cfg(test)]
|
||||
dbg!(&e);
|
||||
dbg!(&_e);
|
||||
|
||||
Error::new(ErrorKind::InvalidData, "Invalid JSON.")
|
||||
})?;
|
||||
@ -228,6 +237,7 @@ mod test {
|
||||
name: "texture.png".to_string(),
|
||||
tags: vec!["Wood".to_string(), "Hair".to_string()],
|
||||
format: TextureFormat::PNG,
|
||||
added_on: Date::new(2019, 10, 12),
|
||||
resolution: (512, 512),
|
||||
texture_hash: Sha256(HASH_BYTES),
|
||||
}
|
||||
@ -387,4 +397,23 @@ mod test {
|
||||
|
||||
test_read_back(&Package::Binary(vec![42u8; 50000]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_correct_serialization() {
|
||||
let mut buffer = Vec::new();
|
||||
serde_json::to_writer(&mut buffer, &demo_texture()).unwrap();
|
||||
let json_value: serde_json::value::Value = serde_json::from_reader(&buffer[..]).unwrap();
|
||||
let expected_value = serde_json::json!(
|
||||
{
|
||||
"id": "d32e1f80-a17a-4dd7-8ed7-c3a2de1de1c9",
|
||||
"name": "texture.png",
|
||||
"tags": ["Wood", "Hair"],
|
||||
"format": "png",
|
||||
"resolution": [512, 512],
|
||||
"added_on": [2019, 10, 12],
|
||||
"texture_hash": "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"
|
||||
}
|
||||
);
|
||||
assert_eq!(json_value, expected_value);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use std::io::*;
|
||||
use std::net::*;
|
||||
|
||||
use std::thread;
|
||||
@ -17,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.read_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();
|
||||
@ -33,6 +33,8 @@ where
|
||||
H: 'static + ProtocolHandler + Sized,
|
||||
{
|
||||
let mut connection = Connection::from_tcp(connection)?;
|
||||
handler.new_connection(&connection);
|
||||
|
||||
'outer: loop {
|
||||
let package = connection.receive()?;
|
||||
|
||||
@ -56,15 +58,17 @@ where
|
||||
}
|
||||
|
||||
Package::Command(Command::Query { query }) => {
|
||||
connection.send(&Package::from(handler.query(&query[..])))?;
|
||||
connection.send(&Package::from(handler.query(&connection, &query[..])))?;
|
||||
}
|
||||
|
||||
Package::Command(Command::GetTexture { id, name }) => match (id, name) {
|
||||
(Some(id), None) => {
|
||||
connection.send(&Package::from(handler.get_texture_by_id(&id)))?;
|
||||
connection.send(&Package::from(handler.get_texture_by_id(&connection, &id)))?;
|
||||
}
|
||||
(None, Some(name)) => {
|
||||
connection.send(&Package::from(handler.get_texture_by_name(&name)))?;
|
||||
connection.send(&Package::from(
|
||||
handler.get_texture_by_name(&connection, &name),
|
||||
))?;
|
||||
}
|
||||
_ => {
|
||||
connection.send(&Package::from(ProtocolError::BadRequest(
|
||||
@ -74,21 +78,25 @@ where
|
||||
},
|
||||
|
||||
Package::Command(Command::GetTextureData { texture_hash }) => {
|
||||
connection.send(&Package::from(handler.get_texture_file(texture_hash)))?;
|
||||
connection.send(&Package::from(
|
||||
handler.get_texture_file(&connection, texture_hash),
|
||||
))?;
|
||||
}
|
||||
|
||||
Package::Command(Command::GetTexturePreview {
|
||||
texture_hash,
|
||||
desired_format,
|
||||
}) => {
|
||||
connection.send(&Package::from(
|
||||
handler.get_texture_preview(texture_hash, desired_format),
|
||||
))?;
|
||||
connection.send(&Package::from(handler.get_texture_preview(
|
||||
&connection,
|
||||
texture_hash,
|
||||
desired_format,
|
||||
)))?;
|
||||
}
|
||||
|
||||
// TODO: use less nesting.
|
||||
Package::Command(Command::ReplaceTexture { old, new }) => {
|
||||
match handler.replace_texture(old.clone(), new.clone(), None) {
|
||||
match handler.replace_texture(&connection, old.clone(), new.clone(), None) {
|
||||
Ok(ReplaceTextureStatus::Ok) => {
|
||||
connection.send(&Package::Json(JsonValue::True))?;
|
||||
}
|
||||
@ -100,12 +108,16 @@ where
|
||||
let pkg = connection.receive()?;
|
||||
match pkg {
|
||||
Package::Binary(data) => {
|
||||
match handler.replace_texture(old.clone(), new.clone(), Some(data))
|
||||
{
|
||||
match handler.replace_texture(
|
||||
&connection,
|
||||
old.clone(),
|
||||
new.clone(),
|
||||
Some(data),
|
||||
) {
|
||||
Ok(ReplaceTextureStatus::Ok) => {
|
||||
connection.send(&Package::Json(JsonValue::True))?;
|
||||
}
|
||||
Ok(ReplaceTextureStatus::NeedTextureData(hash)) => {
|
||||
Ok(ReplaceTextureStatus::NeedTextureData(_hash)) => {
|
||||
panic!("Contract Violation: handler must not return NeedTextureData \
|
||||
when data is given.");
|
||||
}
|
||||
|
@ -46,8 +46,6 @@ pub enum Command {
|
||||
},
|
||||
}
|
||||
|
||||
use super::error::*;
|
||||
|
||||
impl From<ProtocolError> for Package {
|
||||
fn from(item: ProtocolError) -> Self {
|
||||
match item {
|
||||
|
@ -1,35 +1,53 @@
|
||||
mod error;
|
||||
pub use self::error::*;
|
||||
|
||||
mod implementation;
|
||||
pub use self::implementation::*;
|
||||
|
||||
mod autoconnect;
|
||||
pub use self::autoconnect::start_autoconnect_server_async;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::model::*;
|
||||
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait ClientConnection: Display {}
|
||||
|
||||
pub trait ProtocolHandler: Send + Sync + Clone {
|
||||
fn query(&mut self, query: &[String]) -> ProtocolResult<Vec<Texture>>;
|
||||
fn new_connection(&mut self, _con: &ClientConnection) {}
|
||||
|
||||
fn get_texture_by_id(&mut self, id: &str) -> ProtocolResult<Option<Texture>>;
|
||||
fn query(&mut self, con: &ClientConnection, query: &[String]) -> ProtocolResult<Vec<Texture>>;
|
||||
|
||||
fn get_texture_by_name(&mut self, name: &str) -> ProtocolResult<Option<Texture>>;
|
||||
fn get_texture_by_id(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
id: &str,
|
||||
) -> ProtocolResult<Option<Texture>>;
|
||||
|
||||
fn get_texture_file(&mut self, hash: Sha256) -> ProtocolResult<Vec<u8>>;
|
||||
fn get_texture_by_name(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
name: &str,
|
||||
) -> ProtocolResult<Option<Texture>>;
|
||||
|
||||
fn get_texture_file(&mut self, con: &ClientConnection, hash: Sha256)
|
||||
-> ProtocolResult<Vec<u8>>;
|
||||
|
||||
fn get_texture_preview(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
hash: Sha256,
|
||||
format: TextureFormat,
|
||||
) -> ProtocolResult<Vec<u8>>;
|
||||
|
||||
fn replace_texture(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
delete: Option<Texture>,
|
||||
insert: Option<Texture>,
|
||||
insert_texture_data: Option<Vec<u8>>,
|
||||
) -> ProtocolResult<ReplaceTextureStatus>;
|
||||
|
||||
fn disconnected(&mut self, _con: &ClientConnection) {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -38,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 {
|
||||
@ -52,7 +71,8 @@ impl Default for ProtocolConfig {
|
||||
port: 10796,
|
||||
read_timeout_s: 60,
|
||||
write_timeout_s: 75,
|
||||
listen_addr: "::1".to_owned(),
|
||||
listen_addr: "::".to_owned(),
|
||||
autoconnect_multicastv6_addr: "ff02::dd42:c0fe".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
167
server/texture-sync-server/src/search/mod.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use crate::model::*;
|
||||
|
||||
mod query_filter;
|
||||
pub use self::query_filter::QueryFilterSyntaxError;
|
||||
use self::query_filter::*;
|
||||
|
||||
pub struct Query {
|
||||
filters: Vec<QueryFilter>,
|
||||
}
|
||||
|
||||
impl Query {
|
||||
pub fn search(&self, input: &mut Iterator<Item = &Texture>) -> Vec<Texture> {
|
||||
let mut results: Vec<(i64, &Texture)> = Vec::new();
|
||||
|
||||
// We use pseudo decimal fixed point numbers here.
|
||||
// 1.000_000 = 1_000_000;
|
||||
|
||||
// This is done, since algorithms like quicksort can fail on floats.
|
||||
let required_score = self.required_score() * 1_000_000i64;
|
||||
|
||||
'texture_loop: for texture in input {
|
||||
let mut score = 0i64;
|
||||
|
||||
for (pos, filter) in self.filters.iter().enumerate() {
|
||||
match filter.score(texture) {
|
||||
Score::RequiredMatch(true) => (),
|
||||
Score::RequiredMatch(false) => {
|
||||
// skip this texture
|
||||
continue 'texture_loop;
|
||||
}
|
||||
Score::Match(true) => {
|
||||
score += 1_000_000i64 + ((100_000.0 / f64::sqrt(pos as f64 + 1.0)) as i64);
|
||||
}
|
||||
Score::Match(false) => (),
|
||||
}
|
||||
}
|
||||
|
||||
if score >= required_score {
|
||||
// multiply by -1 to get order right.
|
||||
results.push((score * -1, texture))
|
||||
}
|
||||
}
|
||||
|
||||
// We can sort unstable, since at least the IDs are different.
|
||||
results.sort_unstable();
|
||||
|
||||
results
|
||||
.iter()
|
||||
.map(|(_, texture)| Texture::clone(texture))
|
||||
.collect::<Vec<Texture>>()
|
||||
}
|
||||
|
||||
fn required_score(&self) -> i64 {
|
||||
let non_special = self.filters.iter().filter(|f| !f.is_special()).count() as i64;
|
||||
|
||||
// ceil(non_special / 2)
|
||||
(non_special + 1) / 2
|
||||
}
|
||||
|
||||
pub fn parse(input: &[String]) -> Result<Query, QueryFilterSyntaxError> {
|
||||
let mut result = Query { filters: vec![] };
|
||||
|
||||
for fltr in input {
|
||||
let qryfltr = fltr.parse::<QueryFilter>()?;
|
||||
result.filters.push(qryfltr);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// These tests check against the example section in the search design document.
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// just a shorthand
|
||||
fn tex(id: i32, name: &str, tags: &str, added_on: &str, resolution: u64) -> Texture {
|
||||
Texture {
|
||||
id: format!("{}", id), // Id should actaly be a uuid, but for testing this is fine.
|
||||
name: name.to_string(),
|
||||
tags: tags.split(",").map(|s| s.trim().to_string()).collect(),
|
||||
added_on: Date::from_str(added_on).unwrap(),
|
||||
resolution: (resolution, resolution),
|
||||
format: TextureFormat::JPEG,
|
||||
texture_hash: Sha256::from_data(b"Some Hash"),
|
||||
}
|
||||
}
|
||||
|
||||
// data of example section of search document.
|
||||
fn test_data() -> Vec<Texture> {
|
||||
vec![
|
||||
tex(
|
||||
1,
|
||||
"wood_185841",
|
||||
"Holz, Dunkel, Rot, Edel",
|
||||
"2019-05-15",
|
||||
4 * 1024,
|
||||
),
|
||||
tex(2, "wood_84846", "Holz, Hell", "2019-05-13", 2 * 1024),
|
||||
tex(3, "silk_large", "Stoff, Rot, Edel", "2018-01-01", 8 * 1024),
|
||||
tex(4, "cotton_xxx", "Stoff, Rot, Rau", "2018-02-01", 2048),
|
||||
tex(5, "green_frabric", "Grün, Stoff", "2018-03-01", 1024),
|
||||
tex(6, "tin54_45", "Metall, Hell", "2018-03-01", 4 * 1024),
|
||||
tex(7, "copper4_1k", "Rot, Metall", "2016-03-01", 1024),
|
||||
tex(
|
||||
8,
|
||||
"rusty_metall",
|
||||
"Rot, Metall, Rost, Dunkel",
|
||||
"2015-03-01",
|
||||
8 * 1024,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn assert_query(query: &str, expected: Vec<u8>) {
|
||||
let q = Query::parse(
|
||||
&query
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let data = test_data();
|
||||
|
||||
let result = q.search(&mut data.iter());
|
||||
|
||||
let only_ids = result
|
||||
.into_iter()
|
||||
.map(|tex| tex.id.parse::<u8>().unwrap())
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
assert_eq!(only_ids, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_from_doc_1() {
|
||||
assert_query("Holz Dunkel", vec![1, 2, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_from_doc_2() {
|
||||
assert_query("n:wood_", vec![1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_from_doc_3() {
|
||||
assert_query("before:2019-05-31 after:2019-04-30", vec![1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_from_doc_4() {
|
||||
assert_query("Stoff Rot Edel", vec![3, 4, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_from_doc_5() {
|
||||
assert_query("Stoff Rot !Edel", vec![4, 3, 5, 7, 8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_from_doc_6() {
|
||||
assert_query("Metall Dunkel res:4k", vec![8, 6, 1]);
|
||||
}
|
||||
}
|
207
server/texture-sync-server/src/search/query_filter.rs
Normal file
@ -0,0 +1,207 @@
|
||||
use crate::model::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum QueryFilter {
|
||||
Not(Box<QueryFilter>),
|
||||
Tag(String),
|
||||
SpecialInName(String),
|
||||
SpecialBeforeDate(Date),
|
||||
SpecialAfterDate(Date),
|
||||
SpecialMinResolution(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Score {
|
||||
RequiredMatch(bool),
|
||||
Match(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum QueryFilterSyntaxError {
|
||||
UnknownSpecialFilter,
|
||||
ResolutionArgumentInvalid,
|
||||
DateArgumentInvalid,
|
||||
NameArgumentInvalid,
|
||||
ArgumentInvalid,
|
||||
}
|
||||
|
||||
use std::ops::Not;
|
||||
impl Not for Score {
|
||||
type Output = Score;
|
||||
|
||||
fn not(self) -> Score {
|
||||
match self {
|
||||
Score::RequiredMatch(b) => Score::RequiredMatch(!b),
|
||||
Score::Match(b) => Score::Match(!b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryFilter {
|
||||
pub fn is_special(&self) -> bool {
|
||||
use QueryFilter::*;
|
||||
match self {
|
||||
Not(inner) => inner.is_special(),
|
||||
Tag(_) => false,
|
||||
SpecialInName(_) => true,
|
||||
SpecialBeforeDate(_) => true,
|
||||
SpecialAfterDate(_) => true,
|
||||
SpecialMinResolution(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn score(&self, texture: &Texture) -> Score {
|
||||
use QueryFilter::*;
|
||||
match self {
|
||||
Not(inner) => inner.score(texture).not(),
|
||||
|
||||
Tag(tag) => Score::Match(
|
||||
texture
|
||||
.tags
|
||||
.iter()
|
||||
.find(|tt| tt.to_lowercase() == tag.to_lowercase())
|
||||
.is_some(),
|
||||
),
|
||||
|
||||
SpecialInName(name) => Score::RequiredMatch(
|
||||
//
|
||||
texture.name.contains(name),
|
||||
),
|
||||
|
||||
SpecialBeforeDate(date) => Score::RequiredMatch(texture.added_on <= *date),
|
||||
|
||||
SpecialAfterDate(date) => Score::RequiredMatch(texture.added_on > *date),
|
||||
|
||||
SpecialMinResolution(required) => {
|
||||
let smaller_resolution = u64::min(texture.resolution.0, texture.resolution.1);
|
||||
|
||||
Score::RequiredMatch(smaller_resolution >= *required)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for QueryFilter {
|
||||
type Err = QueryFilterSyntaxError;
|
||||
|
||||
fn from_str(input: &str) -> Result<QueryFilter, QueryFilterSyntaxError> {
|
||||
const NEGATION_CHAR: char = '!';
|
||||
const SPLIT_CHAR: char = ':';
|
||||
|
||||
if input.is_empty() {
|
||||
return Err(QueryFilterSyntaxError::ArgumentInvalid);
|
||||
} else if input.starts_with(NEGATION_CHAR) {
|
||||
// A Not.
|
||||
let inner = Self::from_str(&input[1..])?;
|
||||
return Ok(QueryFilter::Not(Box::new(inner)));
|
||||
} else if input.contains(SPLIT_CHAR) {
|
||||
// A Special Filter
|
||||
let mut parts = input.splitn(2, SPLIT_CHAR);
|
||||
let filter_name = parts.next().unwrap().to_lowercase();
|
||||
let filter_arg = parts.next().unwrap();
|
||||
|
||||
match filter_name.as_str() {
|
||||
"n" | "name" => {
|
||||
if filter_arg.is_empty() {
|
||||
return Err(QueryFilterSyntaxError::NameArgumentInvalid);
|
||||
}
|
||||
Ok(QueryFilter::SpecialInName(filter_arg.to_string()))
|
||||
}
|
||||
"r" | "res" | "resolution" => {
|
||||
let num: u64;
|
||||
|
||||
if filter_arg.ends_with("k") {
|
||||
num = *&filter_arg[0..(filter_arg.len() - 1)]
|
||||
.parse::<u32>()
|
||||
.map(|n| (n as u64) * 1024)
|
||||
.map_err(|_e| QueryFilterSyntaxError::ResolutionArgumentInvalid)?;
|
||||
} else {
|
||||
num = filter_arg
|
||||
.parse()
|
||||
.map_err(|_e| QueryFilterSyntaxError::ResolutionArgumentInvalid)?;
|
||||
}
|
||||
|
||||
Ok(QueryFilter::SpecialMinResolution(num))
|
||||
}
|
||||
"a" | "after" => {
|
||||
let date = Date::from_str(filter_arg)
|
||||
.map_err(|_| QueryFilterSyntaxError::DateArgumentInvalid)?;
|
||||
|
||||
Ok(QueryFilter::SpecialAfterDate(date))
|
||||
}
|
||||
"b" | "bef" | "before" => {
|
||||
let date = Date::from_str(filter_arg)
|
||||
.map_err(|_| QueryFilterSyntaxError::DateArgumentInvalid)?;
|
||||
|
||||
Ok(QueryFilter::SpecialBeforeDate(date))
|
||||
}
|
||||
|
||||
_ => Err(QueryFilterSyntaxError::UnknownSpecialFilter),
|
||||
}
|
||||
} else {
|
||||
// A Tag
|
||||
Ok(QueryFilter::Tag(input.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parsing() {
|
||||
assert!(QueryFilter::from_str("cats:meep").is_err());
|
||||
assert!(QueryFilter::from_str("name:").is_err());
|
||||
assert!(QueryFilter::from_str("res:-400k").is_err());
|
||||
assert!(QueryFilter::from_str("res:4647846846846864864846868446864846844684784k").is_err());
|
||||
assert!(QueryFilter::from_str("!!!!a:80-50-50").is_err());
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("n:hello"),
|
||||
Ok(QueryFilter::SpecialInName("hello".to_string()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("NaMe:hello"),
|
||||
Ok(QueryFilter::SpecialInName("hello".to_string()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("res:4k"),
|
||||
Ok(QueryFilter::SpecialMinResolution(4096))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("res:4096"),
|
||||
Ok(QueryFilter::SpecialMinResolution(4096))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("a:2019-10-10"),
|
||||
Ok(QueryFilter::SpecialAfterDate(Date::new(2019, 10, 10)))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("!before:2019-10-10"),
|
||||
Ok(QueryFilter::Not(
|
||||
QueryFilter::SpecialBeforeDate(Date::new(2019, 10, 10)).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("!before:2019-10-10"),
|
||||
Ok(QueryFilter::Not(
|
||||
QueryFilter::SpecialBeforeDate(Date::new(2019, 10, 10)).into()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
QueryFilter::from_str("!Wood"),
|
||||
Ok(QueryFilter::Not(
|
||||
QueryFilter::Tag("Wood".to_string()).into()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
@ -1,50 +1,218 @@
|
||||
// TODO: remove on implementation
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::model::*;
|
||||
use crate::persistency::*;
|
||||
use crate::protocol::*;
|
||||
use crate::search::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::path::Path;
|
||||
use std::sync::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerState {
|
||||
// ...
|
||||
data_store: Arc<RwLock<DataStore>>,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(storage_path: &Path) -> std::io::Result<Self> {
|
||||
Ok(Self {
|
||||
data_store: Arc::new(RwLock::new(DataStore::new(&storage_path)?)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolHandler for ServerState {
|
||||
fn query(&mut self, query: &[String]) -> ProtocolResult<Vec<Texture>> {
|
||||
unimplemented!()
|
||||
fn new_connection(&mut self, con: &ClientConnection) {
|
||||
println!("{}: New Connection", con);
|
||||
}
|
||||
|
||||
fn get_texture_by_id(&mut self, id: &str) -> ProtocolResult<Option<Texture>> {
|
||||
unimplemented!()
|
||||
fn query(&mut self, con: &ClientConnection, query: &[String]) -> ProtocolResult<Vec<Texture>> {
|
||||
let q = Query::parse(query)
|
||||
.map_err(|e| ProtocolError::BadRequest(format!("Invalid Query String: {:?}", e)))?;
|
||||
|
||||
let data_store = self
|
||||
.data_store
|
||||
.read()
|
||||
.unwrap_or_else(|_| handle_broken_rwlock());
|
||||
|
||||
let mut textures = data_store.borrow_textures();
|
||||
|
||||
let found = q.search(&mut textures);
|
||||
|
||||
println!("{}: Query '{:?}' found: {}", con, query, found.len());
|
||||
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
fn get_texture_by_name(&mut self, name: &str) -> ProtocolResult<Option<Texture>> {
|
||||
unimplemented!()
|
||||
fn get_texture_by_id(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
id: &str,
|
||||
) -> ProtocolResult<Option<Texture>> {
|
||||
let data_store = self
|
||||
.data_store
|
||||
.read()
|
||||
.unwrap_or_else(|_| handle_broken_rwlock());
|
||||
|
||||
let found = data_store.texture_by_id(id);
|
||||
|
||||
println!(
|
||||
"{}: Texture by id: '{}' found: {}",
|
||||
con,
|
||||
id,
|
||||
found.is_some()
|
||||
);
|
||||
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
fn get_texture_file(&mut self, hash: Sha256) -> ProtocolResult<Vec<u8>> {
|
||||
unimplemented!()
|
||||
fn get_texture_by_name(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
name: &str,
|
||||
) -> ProtocolResult<Option<Texture>> {
|
||||
let data_store = self
|
||||
.data_store
|
||||
.read()
|
||||
.unwrap_or_else(|_| handle_broken_rwlock());
|
||||
|
||||
let found = data_store.texture_by_name(name);
|
||||
|
||||
println!(
|
||||
"{}: Texture by name: '{}' found: {}",
|
||||
con,
|
||||
name,
|
||||
found.is_some()
|
||||
);
|
||||
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
fn get_texture_file(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
hash: Sha256,
|
||||
) -> ProtocolResult<Vec<u8>> {
|
||||
let data_store = self
|
||||
.data_store
|
||||
.read()
|
||||
.unwrap_or_else(|_| handle_broken_rwlock());
|
||||
|
||||
let data = data_store.read_texture_file_by_hash(&hash);
|
||||
|
||||
println!("{}: Texture File: '{}' found: {}", con, &hash, data.is_ok());
|
||||
|
||||
Ok(data?)
|
||||
}
|
||||
|
||||
fn get_texture_preview(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
hash: Sha256,
|
||||
format: TextureFormat,
|
||||
) -> ProtocolResult<Vec<u8>> {
|
||||
unimplemented!()
|
||||
let mut data_store = self
|
||||
.data_store
|
||||
.write()
|
||||
.unwrap_or_else(|_| handle_broken_rwlock());
|
||||
|
||||
let preview = data_store.get_texture_preview(&hash, format);
|
||||
|
||||
println!(
|
||||
"{}: Texture Preview: '{}' found: {}",
|
||||
con,
|
||||
&hash,
|
||||
preview.is_ok()
|
||||
);
|
||||
|
||||
Ok(preview?)
|
||||
}
|
||||
|
||||
fn replace_texture(
|
||||
&mut self,
|
||||
con: &ClientConnection,
|
||||
delete: Option<Texture>,
|
||||
insert: Option<Texture>,
|
||||
insert_texture_data: Option<Vec<u8>>,
|
||||
) -> ProtocolResult<ReplaceTextureStatus> {
|
||||
// NOTE: must also check if insert_texture_data fits sha256!
|
||||
unimplemented!()
|
||||
println!("{}: Replace Texture Request", con);
|
||||
|
||||
let mut data_store = self
|
||||
.data_store
|
||||
.write()
|
||||
.unwrap_or_else(|_| handle_broken_rwlock());
|
||||
|
||||
match insert_texture_data {
|
||||
Some(data) => {
|
||||
data_store.store_texture_file(&data)?;
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
match (delete, insert) {
|
||||
(Some(delete), Some(insert)) => {
|
||||
if !data_store.is_texture_file_on_disk(&insert.texture_hash) {
|
||||
return Ok(ReplaceTextureStatus::NeedTextureData(insert.texture_hash));
|
||||
}
|
||||
|
||||
if !data_store.delete(&delete) {
|
||||
return Err(ProtocolError::Conflict(
|
||||
"Delete Texture was modified!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if !data_store.insert(insert) {
|
||||
// undo delete
|
||||
// panics if texture file is delete during delete and reinsert.
|
||||
// unlikely to happen.
|
||||
assert!(data_store.insert(delete));
|
||||
return Err(ProtocolError::Conflict(
|
||||
"Name or Id already taken.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
data_store.flush_metadata()?;
|
||||
|
||||
Ok(ReplaceTextureStatus::Ok)
|
||||
}
|
||||
(Some(delete), None) => {
|
||||
if !data_store.delete(&delete) {
|
||||
return Err(ProtocolError::Conflict(
|
||||
"Delete Texture was modified!".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
data_store.flush_metadata()?;
|
||||
|
||||
Ok(ReplaceTextureStatus::Ok)
|
||||
}
|
||||
(None, Some(insert)) => {
|
||||
if !data_store.is_texture_file_on_disk(&insert.texture_hash) {
|
||||
return Ok(ReplaceTextureStatus::NeedTextureData(insert.texture_hash));
|
||||
}
|
||||
|
||||
if !data_store.insert(insert) {
|
||||
// undo delete
|
||||
return Err(ProtocolError::Conflict(
|
||||
"Name or Id already taken.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
data_store.flush_metadata()?;
|
||||
|
||||
Ok(ReplaceTextureStatus::Ok)
|
||||
}
|
||||
(None, None) => Ok(ReplaceTextureStatus::Ok),
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnected(&mut self, con: &ClientConnection) {
|
||||
println!("{}: Disconnected", con);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_broken_rwlock() -> ! {
|
||||
eprintln!("Panic while data_store lock was acquired.");
|
||||
eprintln!("Representation in memory could be invalid.");
|
||||
eprintln!("Can't recover. Please restart.");
|
||||
std::process::exit(13);
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|