From d47625fead650375bde89ceeca04e0f21bd79d85 Mon Sep 17 00:00:00 2001 From: ervelae <97437577+ervelae@users.noreply.github.com> Date: Wed, 12 Jan 2022 19:12:20 +0200 Subject: [PATCH] Initial commit :) --- Project 1/.gitignore | 20 + Project 1/.gitlab-ci.yml | 13 + Project 1/README.md | 39 ++ Project 1/build.sbt | 236 +++++++++ Project 1/pom.xml | 452 ++++++++++++++++++ Project 1/project/build.properties | 1 + Project 1/project/plugins.sbt | 7 + .../java/fi/utu/tech/gui/javafx/Dialogs.java | 13 + .../utu/tech/gui/javafx/FXMLController.java | 63 +++ .../utu/tech/gui/javafx/FXMLController2.java | 83 ++++ .../java/fi/utu/tech/gui/javafx/Main.java | 29 ++ .../java/fi/utu/tech/gui/javafx/MainApp.java | 59 +++ .../fi/utu/tech/gui/javafx/MainApp10.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp4.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp5.java | 62 +++ .../fi/utu/tech/gui/javafx/OtherWindow.java | 47 ++ .../fi/utu/tech/gui/javafx/OtherWindow10.java | 48 ++ .../fi/utu/tech/gui/javafx/OtherWindow4.java | 50 ++ .../utu/tech/gui/javafx/ResourceLoader.java | 42 ++ .../fi/utu/tech/gui/javafx/Teht_5_ikkuna.java | 80 ++++ .../resources/fi/utu/tech/gui/javafx/hmm.png | Bin 0 -> 9965 bytes .../fi/utu/tech/gui/javafx/otherwindow.fxml | 45 ++ .../fi/utu/tech/gui/javafx/scene.fxml | 48 ++ .../fi/utu/tech/gui/javafx/styles.css | 3 + .../fi/utu/tech/gui/javafx/styles2.css | 7 + Project 2/.gitignore | 21 + Project 2/.gitlab-ci.yml | 13 + Project 2/README.md | 39 ++ Project 2/build.sbt | 236 +++++++++ Project 2/pom.xml | 452 ++++++++++++++++++ Project 2/project/build.properties | 1 + Project 2/project/plugins.sbt | 7 + .../main/java/fi/utu/tech/gui/javafx/BMI.java | 46 ++ .../fi/utu/tech/gui/javafx/CanvasWindow.java | 84 ++++ .../java/fi/utu/tech/gui/javafx/Dialogs.java | 13 + .../utu/tech/gui/javafx/FXMLController.java | 63 +++ .../java/fi/utu/tech/gui/javafx/Main.java | 29 ++ .../java/fi/utu/tech/gui/javafx/MainApp.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp1.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp2.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp3.java | 76 +++ .../java/fi/utu/tech/gui/javafx/MainApp4.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp5.java | 59 +++ .../java/fi/utu/tech/gui/javafx/MainApp6.java | 59 +++ .../fi/utu/tech/gui/javafx/OtherWindow.java | 14 + .../utu/tech/gui/javafx/PasswordWindow.java | 91 ++++ .../utu/tech/gui/javafx/ResourceLoader.java | 42 ++ .../utu/tech/gui/javafx/StackPaneWindow.java | 41 ++ .../fi/utu/tech/gui/javafx/WebSelain.java | 40 ++ .../resources/fi/utu/tech/gui/javafx/hmm.png | Bin 0 -> 9965 bytes .../fi/utu/tech/gui/javafx/scene.fxml | 48 ++ .../fi/utu/tech/gui/javafx/styles.css | 3 + Project 3/.gitignore | 20 + Project 3/.gitlab-ci.yml | 13 + Project 3/README.md | 39 ++ Project 3/build.sbt | 236 +++++++++ Project 3/pom.xml | 452 ++++++++++++++++++ Project 3/project/build.properties | 1 + Project 3/project/plugins.sbt | 7 + .../java/fi/utu/tech/gui/javafx/Dialogs.java | 13 + .../java/fi/utu/tech/gui/javafx/Main.java | 31 ++ .../java/fi/utu/tech/gui/javafx/MainApp.java | 40 ++ .../utu/tech/gui/javafx/ResourceLoader.java | 42 ++ .../gui/javafx/gimmick/GimmickController.java | 27 ++ .../utu/tech/gui/javafx/gimmick/MainApp2.java | 20 + .../fi/utu/tech/gui/javafx/gimmick/View.java | 17 + .../fi/utu/tech/gui/javafx/gimmick/View1.java | 63 +++ .../fi/utu/tech/gui/javafx/gimmick/View2.java | 36 ++ .../utu/tech/gui/javafx/zipper/MainApp1.java | 27 ++ .../fi/utu/tech/gui/javafx/zipper/Zipper.java | 134 ++++++ .../gui/javafx/zipper/ZipperController.java | 112 +++++ .../fi/utu/tech/gui/javafx/alien.png | Bin 0 -> 46129 bytes .../fi/utu/tech/gui/javafx/gimmick.fxml | 54 +++ .../resources/fi/utu/tech/gui/javafx/hmm.png | Bin 0 -> 8752 bytes .../fi/utu/tech/gui/javafx/package.png | Bin 0 -> 8960 bytes .../fi/utu/tech/gui/javafx/styles.css | 7 + .../fi/utu/tech/gui/javafx/zipper.fxml | 70 +++ 77 files changed, 4739 insertions(+) create mode 100644 Project 1/.gitignore create mode 100644 Project 1/.gitlab-ci.yml create mode 100644 Project 1/README.md create mode 100644 Project 1/build.sbt create mode 100644 Project 1/pom.xml create mode 100644 Project 1/project/build.properties create mode 100644 Project 1/project/plugins.sbt create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController2.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/Main.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp10.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow10.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow4.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java create mode 100644 Project 1/src/main/java/fi/utu/tech/gui/javafx/Teht_5_ikkuna.java create mode 100644 Project 1/src/main/resources/fi/utu/tech/gui/javafx/hmm.png create mode 100644 Project 1/src/main/resources/fi/utu/tech/gui/javafx/otherwindow.fxml create mode 100644 Project 1/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml create mode 100644 Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles.css create mode 100644 Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles2.css create mode 100644 Project 2/.gitignore create mode 100644 Project 2/.gitlab-ci.yml create mode 100644 Project 2/README.md create mode 100644 Project 2/build.sbt create mode 100644 Project 2/pom.xml create mode 100644 Project 2/project/build.properties create mode 100644 Project 2/project/plugins.sbt create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/BMI.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/CanvasWindow.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/Main.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp1.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp2.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp3.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp6.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/PasswordWindow.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/StackPaneWindow.java create mode 100644 Project 2/src/main/java/fi/utu/tech/gui/javafx/WebSelain.java create mode 100644 Project 2/src/main/resources/fi/utu/tech/gui/javafx/hmm.png create mode 100644 Project 2/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml create mode 100644 Project 2/src/main/resources/fi/utu/tech/gui/javafx/styles.css create mode 100644 Project 3/.gitignore create mode 100644 Project 3/.gitlab-ci.yml create mode 100644 Project 3/README.md create mode 100644 Project 3/build.sbt create mode 100644 Project 3/pom.xml create mode 100644 Project 3/project/build.properties create mode 100644 Project 3/project/plugins.sbt create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/Main.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/MainApp.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/GimmickController.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/MainApp2.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View1.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View2.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/MainApp1.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/Zipper.java create mode 100644 Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/ZipperController.java create mode 100644 Project 3/src/main/resources/fi/utu/tech/gui/javafx/alien.png create mode 100644 Project 3/src/main/resources/fi/utu/tech/gui/javafx/gimmick.fxml create mode 100644 Project 3/src/main/resources/fi/utu/tech/gui/javafx/hmm.png create mode 100644 Project 3/src/main/resources/fi/utu/tech/gui/javafx/package.png create mode 100644 Project 3/src/main/resources/fi/utu/tech/gui/javafx/styles.css create mode 100644 Project 3/src/main/resources/fi/utu/tech/gui/javafx/zipper.fxml diff --git a/Project 1/.gitignore b/Project 1/.gitignore new file mode 100644 index 0000000..5bfb48e --- /dev/null +++ b/Project 1/.gitignore @@ -0,0 +1,20 @@ +contracts/ +.classpath +.project +.history/ +.idea +.jqwik-database +.lib/ +.worksheet +.settings/ +*.iml +*.ipr +*.iws +*.log +project/boot/ +project/plugins/project/ +project/project/ +project/*-shim.sbt +project/target/ +target/ +openjfx/ diff --git a/Project 1/.gitlab-ci.yml b/Project 1/.gitlab-ci.yml new file mode 100644 index 0000000..4b3553b --- /dev/null +++ b/Project 1/.gitlab-ci.yml @@ -0,0 +1,13 @@ +image: maven:latest + +stages: + - build + - test + +build: + script: + - mvn compile + +test: + script: + - mvn test \ No newline at end of file diff --git a/Project 1/README.md b/Project 1/README.md new file mode 100644 index 0000000..2907fd1 --- /dev/null +++ b/Project 1/README.md @@ -0,0 +1,39 @@ +# Project description + +Simple template for projects that make use of JavaFX and FXML (Scene Builder). +Requires Java 11 or later. Compatible with +Eclipse and IntelliJ IDEA. Minor issues with Netbeans. Automatically +integrates with Gitlab CI. + +## Installation + +Maven: + +```bash +$ git clone https://gitlab.utu.fi/tech/education/gui/template-javafx + +$ cd template-javafx + +$ mvn compile exec:java +``` + +SBT: + +```bash +$ git clone https://gitlab.utu.fi/tech/education/gui/template-javafx + +$ cd template-javafx + +$ sbt compile run +``` + +## Further instructions + + * Java platform: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/jvm-platform + * Maven: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/maven-misc + * SBT: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/sbt-misc + +External sources: + + * JavaFX: https://openjfx.io/javadoc/11/ + * Scene Builder: https://docs.gluonhq.com/scenebuilder/ diff --git a/Project 1/build.sbt b/Project 1/build.sbt new file mode 100644 index 0000000..e703e90 --- /dev/null +++ b/Project 1/build.sbt @@ -0,0 +1,236 @@ +// Project template + +// Supported operating systems: Windows, Mac, Linux +// Supported JDKs: 8, 10+ + +// Project name +name := "template-javafx" + +// organization name +organization := "fi.utu.tech" + +version := "1.0" + +// project description +description := "JavaFX project template" + +// main class +Compile/mainClass := Some("fi.utu.tech.gui.javafx.Main") + +// force the java version by typing it here (remove the comment) +val force_javaVersion = None // Some(13) + +// force the javafx version by typing it here (remove the comment) +val force_javaFxVersion = None // Some(13) + +val useJavaFX = true + +val useScalaOrScalaFX = true + +// END_OF_SIMPLE_CONFIGURATION +// you can copy the rest for each new project +// --- --- --- + +def fail(msg: String) = { + println("Error :-/") + println + println(msg) + System.exit(1) + null +} + +val detectedJDK = System.getProperty("java.version").replace("-ea","").split('.').dropWhile(_.toInt<8).head.toInt + +val javaVersionNum = force_javaVersion.getOrElse(detectedJDK) + +val javaVersionString = javaVersionNum match { + case 7 => "1.7" + case 8 => "1.8" + case x if x > 8 => x.toString +} + +val lts = 11 +val dev = 13 + +val supported = javaVersionNum match { + case x if x < 8 => fail("Your Java installation is obsolete. Please upgrade to Java " + lts + "LTS") + case 9 => fail("Your Java installation is unsupported and has known issues. Please upgrade to Java " + lts + "LTS") + case x if x < lts => println("Consider upgrading to Java " + lts + " LTS"); true + case x if x > lts && x < dev => println("Consider upgrading to Java " + dev); true + case x if x > dev => println("Unsupported early access version. Consider switching back to Java " + dev); true + case _ => true +} + +javacOptions ++= Seq("-source", javaVersionString, "-target", javaVersionString, "-encoding", "utf8", "-Xlint:unchecked", "-Xlint:deprecation") +javacOptions in doc := Seq("-source", javaVersionString) + +enablePlugins(JShellPlugin) + +compileOrder := CompileOrder.JavaThenScala + +// Enables publishing to maven repo +publishMavenStyle := true + +// Do not append Scala versions to the generated artifacts +crossPaths := false + +// This forbids including Scala related libraries into the dependency +autoScalaLibrary := false + +assemblyMergeStrategy in assembly := { + case PathList("META-INF", xs @ _*) => MergeStrategy.discard + case _ => MergeStrategy.first +} + +// contains libraries provided by utu/ft dep +resolvers += "ftdev" at "https://ftdev.utu.fi/maven2" + +fork in Global := true + +val javaVersion = taskKey[Unit]("Prints the Java version.") + +javaVersion := { println("SBT uses Java SDK located at "+System.getProperty("java.home")) } + +publishTo := Some(Resolver.file("file", new File("/tmp/repository"))) + +val oomkit = "fi.utu.tech" % "oomkit" % "1.15" + +libraryDependencies ++= Seq() + +//// +//// JQWIK / JUNIT configuration +//// + +resolvers in ThisBuild += Resolver.jcenterRepo + +val junit_version = "5.5.2" + +// library dependencies. (orginization name) % (project name) % (version) +libraryDependencies ++= Seq( + "net.aichler" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test, + "org.junit.platform" % "junit-platform-commons" % ("1"+junit_version.tail) % Test, + "org.junit.platform" % "junit-platform-runner" % ("1"+junit_version.tail) % Test, + "org.junit.jupiter" % "junit-jupiter-engine" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-api" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-migrationsupport" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-params" % junit_version % Test, + "net.jqwik" % "jqwik" % "1.2.0" % Test, + "org.scalatest" %% "scalatest" % "3.0.8" % Test, +) + +testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-c") + +//// +//// JAVAFX configuration +//// + +val javafx_versions = if (!useJavaFX) (0,"-","-") else (force_javaFxVersion getOrElse javaVersionNum) match { + case 7 => (7, "7", "8.0.181-R13") + case 8 => (8, "8", "8.0.181-R13") + case 10 => (11, "11.0.2", "11-R16") + case x if x>10 => (13, "13", "12.0.2-R18") + case _ => fail("Unsupported Java version for JavaFX") +} + +// JAVA_HOME location +val javaHomeDir = { + val path = try { + if (scala.sys.env("JAVA_HOME").trim.isEmpty) throw new Exception("Empty JAVA_HOME") else scala.sys.env("JAVA_HOME") + } catch { + case _: Throwable => System.getProperty("java.home") // not set -> ask from current JVM + } + + val f = file(path) + if (!f.exists()) fail("The environment variable JAVA_HOME points to a non-existent directory!\nSolution: Edit your system settings (Windows control panel / *nix .bashrc) and fix the JAVA_HOME location.") + f +} + +val osName: SettingKey[String] = SettingKey[String]("osName") + +osName := (System.getProperty("os.name") match { + case n if n.startsWith("Linux") => "linux" + case n if n.startsWith("Mac") => "mac" + case n if n.startsWith("Windows") => "win" + case _ => throw new Exception("Unknown platform!") +}) + +def legacyJavaFX() = { + val searchDirs = Seq( + "/jre/lib/jfxrt.jar", // OpenJDK 7 + "/jre/lib/ext/jfxrt.jar", // OpenJDK 8 + "/lib/ext/jfxrt.jar" // Windows & Oracle Java 8 + ) + + if (detectedJDK > 8) fail(s"Trying to use legacy non-modular JavaFX with a modern JDK [$detectedJDK].\nSolution: Check the line 'val force_javaFxVersion =' in build.sbt.") + + val javaFxJAR = searchDirs.map{ searchDir => file(javaHomeDir + searchDir) }.find{ _.exists() } + + javaFxJAR.getOrElse { + fail(s"Java FX runtime not installed in [${javaHomeDir.toString}]!\nSolution: Install JavaFX or consider upgrading your JDK so that JavaFX can be installed automatically.") + } +} + +val jfx_sdk_version = javafx_versions._2 +val jfx_scalafx_version = javafx_versions._3 + +val javaFxPath = Def.taskKey[File]("OpenJFX fetcher") +javaFxPath := { + val javaFxHome = + try { + val envHome = file(scala.sys.env("JAVAFX_HOME")) + if (envHome.toString.trim.isEmpty) throw new Exception("Empty JAVAFX_HOME") + println("Using OpenJFX from " + envHome) + envHome + } + catch { case _: Throwable => + println("Using local OpenJFX") + baseDirectory.value / "openjfx" + } + + if (!javaFxHome.exists()) java.nio.file.Files.createDirectory(javaFxHome.toPath) + + val jfx_os = osName.value match { + case "linux" => "linux" + case "mac" => "osx" + case "win" => "windows" + } + + val sdkURL = "http://download2.gluonhq.com/openjfx/" + jfx_sdk_version + "/openjfx-" + jfx_sdk_version + "_" + jfx_os + "-x64_bin-sdk.zip" + + try { + val testDir = javaFxHome / "all.ok" + if (!testDir.exists()) { + println("Fetching OpenJFX from "+sdkURL+"..") + IO.unzipURL(new URL(sdkURL), javaFxHome) + java.nio.file.Files.createDirectory(testDir.toPath) + println("Fetching OpenJFX done.") + } else { + println("Using OpenJFX from "+javaFxHome) + } + + javaFxHome + } + catch { + case t: Throwable => fail("Could not load OpenJFX! Reason:" + t.getMessage) + } +} + +val jfxModules = Seq("base","controls","fxml","graphics","media","swing","web") + + +if (!useJavaFX) Seq() else javafx_versions._1 match { + case 7 => + // TODO libraryDependencies + Seq(unmanagedJars in Compile += Attributed.blank(legacyJavaFX())) + case 8 => + (if (useScalaOrScalaFX) Seq(libraryDependencies += "org.scalafx" %% "scalafx" % jfx_scalafx_version) else Seq()) ++ + Seq(unmanagedJars in Compile += Attributed.blank(legacyJavaFX())) + case _ => + Seq( + javaOptions in run ++= Seq( + "--module-path", (javaFxPath.value / ("javafx-sdk-" + jfx_sdk_version) / "lib").toString, + "--add-modules=" + jfxModules.map("javafx."+_).mkString(",")) + ) ++ + (if (useScalaOrScalaFX) Seq(libraryDependencies += "org.scalafx" % "scalafx_2.13" % jfx_scalafx_version) else Seq()) ++ + jfxModules.map(module => libraryDependencies += "org.openjfx" % ("javafx-"+module) % jfx_sdk_version classifier osName.value) +} diff --git a/Project 1/pom.xml b/Project 1/pom.xml new file mode 100644 index 0000000..40e1bb8 --- /dev/null +++ b/Project 1/pom.xml @@ -0,0 +1,452 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <!-- + + Welcome! + This pom.xml is *THE* file that defines your Maven style Java project. + + Eclipse, IDEA and other development environments with Maven support + or plugins can *import* this project by reading this file. + + It usually contains tons of barely readable configuration. Luckily + this basic pom.xml is somewhat readable. All necessary configuration + for customizing your project if located here on top before the + 'END OF SIMPLE CONFIGURATION' line below. + + Some basics: + + - Maven is a build system for Java/JVM + - the Maven projects define an artifact that has a three part id: + groupId - artifactId - version + + - For example, this project is called: + fi.utu.tech - oomkit - 1.15 + + - The group id is usually a web domain in reverse order. + + - You can use these id parts to search for projects at + https://search.maven.org/ + + - If your version is not final and you have plans to modify the + source code at some point, please use a version id that ends with + -SNAPSHOT, e.g 1.0-SNAPSHOT. Maven may cache the project jar + and refuse to overwrite old cached versions with new ones unless + you remember this convention. + + - This file supports + compiling the project with 'mvn compile' (see target/) + cleaning the class files with 'mvn clean' + executing the main routine with 'mvn exec:java' + executing the unit tests with 'mvn test' + packaging the application with 'mvn package' (see target/) + + - the Maven project structure is as follows: + https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html + + --> + + <!-- ==== START OF SIMPLE CONFIGURATION ==== --> + + <!-- the three parts of the artifact name --> + <groupId>fi.utu.tech</groupId> + <artifactId>template-javafx</artifactId> + <version>1.0</version> + + <!-- additional information about the project --> + <name>JavaFX project template</name> + <url>https://gitlab.utu.fi/tech/education/gui/template-javafx</url> + + <packaging>jar</packaging> + + <!-- HINT: More configuration here! --> + <properties> + <!-- Configures this project to use 'fi.utu.tech.AppMain' as its main class --> + <project.mainclass>fi.utu.tech.gui.javafx.Main</project.mainclass> + + + <!-- Don't touch these unless you know what you're doing! + + For example, the source encoding should always be utf-8. + You're probably doing something stupid if you think it + should be a 8-bit code page in 2019. --> + <jdk.version>11</jdk.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <jqwik.version>1.2.0</jqwik.version> + <junit.version>5.5.2</junit.version> + <junitplatform.version>1.5.2</junitplatform.version> + <javafx.version>13</javafx.version> + </properties> + + <!-- ==== END OF SIMPLE CONFIGURATION ==== --> + + <repositories> + <repository> + <snapshots> + <enabled>false</enabled> + </snapshots> + <id>central</id> + <name>Central Repository</name> + <url>https://repo.maven.apache.org/maven2</url> + </repository> + <repository> + <id>jcenter</id> + <name>jcenter</name> + <url>https://jcenter.bintray.com/</url> + </repository> + <!-- UTU repository --> + <repository> + <id>ftdev</id> + <name>ftdev</name> + <url>https://ftdev.utu.fi/maven2</url> + </repository> + </repositories> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-commons</artifactId> + <version>${junitplatform.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>net.jqwik</groupId> + <artifactId>jqwik</artifactId> + <version>${jqwik.version}</version> + <scope>test</scope> + </dependency><!-- + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-migrationsupport</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-runner</artifactId> + <version>${junitplatform.version}</version> + <scope>test</scope> + </dependency> --> + + <!-- JavaFX (remove if not needed to speed up dep downloads)--> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-base</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-fxml</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-media</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-swing</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-web</artifactId> + <version>${javafx.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Make a 'fat' jar, that is, jar that contains all its dependencies and runs as is. + See: https://stackoverflow.com/a/57691362 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>module-info.class</exclude> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>${project.mainclass}</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + + <!-- Run this app with exec:java --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.6.0</version> + <executions> + <execution> + <goals> + <goal>java</goal> + </goals> + </execution> + </executions> + <configuration> + <mainClass>${project.mainclass}</mainClass> + <arguments> + <argument>arg1</argument> + <argument>arg2</argument> + </arguments> + </configuration> + </plugin> + + <!-- Make the packaged jar executable --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.1.2</version> + <configuration> + <!-- DO NOT include log4j.properties file in your Jar --> + <excludes> + <exclude>**/log4j.properties</exclude> + </excludes> + <archive> + <manifest> + <!-- Jar file entry point --> + <mainClass>${project.mainclass}</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + + <!-- download source code in Eclipse, best practice --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + <version>2.10</version> + <configuration> + <downloadSources>true</downloadSources> + <downloadJavadocs>false</downloadJavadocs> + </configuration> + </plugin> + + <!-- JDK source/target versions --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + <configuration> + <source>${jdk.version}</source> + <target>${jdk.version}</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>3.1.1</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + <configuration> + <tags> + <tag> + <name>toDo</name> + <placement>a</placement> + <head>To do:</head> + </tag> + <tag> + <name>classInvariant</name> + <placement>t</placement> + <head>Class invariant:</head> + </tag> + <tag> + <name>classInvariantProtected</name> + <placement>t</placement> + <head>Protected class invariant:</head> + </tag> + <tag> + <name>classInvariantPrivate</name> + <placement>t</placement> + <head>Private class invariant:</head> + </tag> + <tag> + <name>abstractionFunction</name> + <placement>t</placement> + <head>Abstraction function:</head> + </tag> + <tag> + <name>pre</name> + <placement>cm</placement> + <head>Precondition:</head> + </tag> + <tag> + <name>post</name> + <placement>cm</placement> + <head>Postcondition:</head> + </tag> + <tag> + <name>postProtected</name> + <placement>cm</placement> + <head>Protected postcondition:</head> + </tag> + <tag> + <name>postPrivate</name> + <placement>cm</placement> + <head>Private postcondition:</head> + </tag> + <tag> + <name>time</name> + <placement>cmf</placement> + <head>Time complexity:</head> + </tag> + <tag> + <name>space</name> + <placement>cmf</placement> + <head>Space complexity:</head> + </tag> + <tag> + <name>correspondence</name> + <placement>a</placement> + <head>Correspondence:</head> + </tag> + <tag> + <name>download</name> + <placement>a</placement> + <head>Download:</head> + </tag> + </tags> + <show>protected</show> + <failOnError>false</failOnError> + <sourceFileExcludes> + <sourceFileExclude>**/module-info.java</sourceFileExclude> + </sourceFileExcludes> + </configuration> + </plugin> + + <!-- JUnit & JQwik test integration --> + + <!-- junit-platform-maven-plugin: supports modular tests + maven-surefire-plugin: non-modular tests + + Modular testing works via command line mvn, but is + still broken in Eclipse due to this + https://bugs.eclipse.org/bugs/show_bug.cgi?id=520667 + --> + <plugin> + <groupId>de.sormuras.junit</groupId> + <artifactId>junit-platform-maven-plugin</artifactId> + <version>1.0.0-M5</version> + <extensions>true</extensions> + <configuration> + <executor>JAVA</executor> + </configuration> + </plugin> + + <!-- + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> --> + + <!-- javafx:jlink: + https://github.com/openjfx/javafx-maven-plugin + Use 'mvn package' instead if you don't need jlink. + + <plugin> + <groupId>org.openjfx</groupId> + <artifactId>javafx-maven-plugin</artifactId> + <version>0.0.3</version> + <configuration> + <mainClass>${project.mainclass}</mainClass> + </configuration> + </plugin> --> + + </plugins> + <extensions> + <!-- Enables the use of SSH for deployments --> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh-external</artifactId> + <version>3.3.3</version> + </extension> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh</artifactId> + <version>3.3.3</version> + </extension> + </extensions> + </build> + <distributionManagement> + <repository> + <id>ftdev</id> + <name>UTU tech ftdev repository</name> + <url>scp://localhost:2222/var/www/maven2</url> + </repository> + </distributionManagement> +</project> diff --git a/Project 1/project/build.properties b/Project 1/project/build.properties new file mode 100644 index 0000000..6adcdc7 --- /dev/null +++ b/Project 1/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.3 diff --git a/Project 1/project/plugins.sbt b/Project 1/project/plugins.sbt new file mode 100644 index 0000000..37ca261 --- /dev/null +++ b/Project 1/project/plugins.sbt @@ -0,0 +1,7 @@ +resolvers += Resolver.jcenterRepo + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9") +addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.8.3") +addSbtPlugin("com.github.xuwei-k" % "sbt-jshell" % "0.1.2") +//addSbtPlugin("org.xerial.sbt" % "sbt-sql-sqlite" % "0.8") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.4.0") diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java new file mode 100644 index 0000000..a4d0180 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java @@ -0,0 +1,13 @@ +package fi.utu.tech.gui.javafx; + +import javafx.scene.control.Alert; + +public class Dialogs { + public static void warning(String title, String header, String content) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + } +} \ No newline at end of file diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java new file mode 100644 index 0000000..7777d66 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java @@ -0,0 +1,63 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Platform; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.stage.Stage; + +import java.util.function.Supplier; + +public class FXMLController { + private IntegerProperty clicks = new SimpleIntegerProperty(0); + + private Supplier<Parent> supplier; + + @FXML + private Label clicksLabel; + + protected void updateClicks() { + if (!clicksLabel.textProperty().isBound()) + clicksLabel.textProperty().bind(clicks.asString().concat(" clicks.")); + + clicks.setValue(clicks.getValue() + 1); + } + + protected void setLabel(String text) { + if (!clicksLabel.textProperty().isBound()) + clicksLabel.setText(text); + } + + @FXML + protected void handleDialogButton(ActionEvent event) { + updateClicks(); + Dialogs.warning("Example dialog", "Content header", "Content"); + } + + @FXML + protected void handleExitButton(ActionEvent event) { + System.out.println("Closing app."); + Platform.exit(); + } + + @FXML + protected void handleWindowButton(ActionEvent event) { + updateClicks(); + Scene other = new Scene(supplier.get()); + Stage otherStage = new Stage(); + otherStage.setScene(other); + otherStage.show(); + } + + protected void setWindowFactory(Supplier<Parent> supplier) { + this.supplier = supplier; + } + + public void initialize() { + System.out.println("2-A"); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController2.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController2.java new file mode 100644 index 0000000..11ce009 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/FXMLController2.java @@ -0,0 +1,83 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Platform; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.stage.Stage; + +import java.util.function.Supplier; + +public class FXMLController2 { + private IntegerProperty clicks = new SimpleIntegerProperty(0); + + private Supplier<Parent> supplier; + private Parent parent; + private int ikkunoita_avattu = 0; + private Scene other; + private Stage otherStage; + + + private Label clicksLabel; + + protected void updateClicks() { + if (!clicksLabel.textProperty().isBound()) + clicksLabel.textProperty().bind(clicks.asString().concat(" clicks.")); + + clicks.setValue(clicks.getValue() + 1); + } + + protected void setLabel(Label l) {clicksLabel = l;} + + + protected void setLabel(String text) { + if (!clicksLabel.textProperty().isBound()) + clicksLabel.setText(text); + } + + protected void handleDialogButton(ActionEvent event) { + updateClicks(); + Dialogs.warning("Example dialog", "Content header", "Content"); + } + + + protected void handleExitButton(ActionEvent event) { + System.out.println("Closing app."); + Platform.exit(); + } + + protected void handleWindowButton(ActionEvent event) { + if(ikkunoita_avattu < 1) { + setLabel("Welcome!"); + updateClicks(); + ResourceLoader<Parent, FXMLController2> loader = new ResourceLoader<>("otherwindow.fxml"); + other = new Scene(loader.root); + otherStage = new Stage(); + otherStage.setScene(other); + otherStage.show(); + System.out.println("Uusi ikkuna avattu"); + ikkunoita_avattu += 1; + } + else if(otherStage.isShowing()){ + otherStage.hide(); + } + else if (!otherStage.isShowing() || !otherStage.isFocused()) { + otherStage.show(); + otherStage.toFront(); + } + } + + protected void setWindowFactory(Supplier<Parent> supplier) { + this.supplier = supplier; + } + + protected void setParent(Parent p) {parent = p;System.out.println(parent + " parent asetettu");} + + public void initialize() { + System.out.println("2-A"); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/Main.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/Main.java new file mode 100644 index 0000000..f3f3688 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/Main.java @@ -0,0 +1,29 @@ +package fi.utu.tech.gui.javafx; + +public class Main { + /** + * The main() method is ignored in correctly deployed JavaFX application. + * main() serves only as fallback in case the application can not be + * launched through deployment artifacts, e.g., in IDEs with limited FX + * support. NetBeans ignores main(). + * + * @param args the command line arguments + */ + public static void main(String[] args) { + System.out.println("Stage A"); + if (args.length == 1 && args[0].equals("--test")) return; + + int exercise = 1; + if (args.length == 1) exercise = Integer.parseInt(args[0]); + switch(exercise) { + case 1: + case 2: + case 3: + case 4: MainApp.launch(MainApp4.class, args); break; + case 5: MainApp.launch(MainApp5.class, args); break; + case 10: MainApp.launch(MainApp10.class, args); break; + default: MainApp.launch(MainApp.class, args); break; + } + } + +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp.java new file mode 100644 index 0000000..6dc8ba3 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new OtherWindow(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp10.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp10.java new file mode 100644 index 0000000..3875e03 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp10.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp10 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new OtherWindow10(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java new file mode 100644 index 0000000..3833d30 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp4 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new OtherWindow4(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java new file mode 100644 index 0000000..921f7d7 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java @@ -0,0 +1,62 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp5 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new Teht_5_ikkuna(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + + //Teht_5_ikkuna loader = new Teht_5_ikkuna(); + + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java new file mode 100644 index 0000000..3f64448 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java @@ -0,0 +1,47 @@ +package fi.utu.tech.gui.javafx; + +import java.io.IOException; + +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.layout.StackPane; + + +public class OtherWindow extends BorderPane { + public OtherWindow() { + Circle c = new Circle(200, Color.NAVY); + setLeft(new ImageView(ResourceLoader.image("hmm.png"))); + + Label terve = new Label("Terve"); + setAlignment(terve,Pos.CENTER); + setTop(terve); + + Label maailma = new Label("Maailma"); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(c, maailma); + setCenter(stackPane); + + TextField textField1 = new TextField(); + TextField textField2 = new TextField(); + TextField textField3 = new TextField(); + HBox hBox = new HBox(textField1,textField2,textField3); + setBottom(hBox); + + + + + + + + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow10.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow10.java new file mode 100644 index 0000000..158757f --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow10.java @@ -0,0 +1,48 @@ +package fi.utu.tech.gui.javafx; + +import java.io.IOException; + +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.layout.StackPane; + + +public class OtherWindow10 extends BorderPane { + public OtherWindow10() { + Circle c = new Circle(200, Color.NAVY); + setLeft(new ImageView(ResourceLoader.image("hmm.png"))); + + Label terve = new Label("Terve"); + setAlignment(terve,Pos.CENTER); + setTop(terve); + + Label maailma = new Label("Maailma"); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(c, maailma); + setCenter(stackPane); + + TextField textField1 = new TextField(); + TextField textField2 = new TextField(); + TextField textField3 = new TextField(); + HBox hBox = new HBox(textField1,textField2,textField3); + setBottom(hBox); + + + textField1.textProperty().bindBidirectional(textField2.textProperty()); + textField2.textProperty().bindBidirectional(textField3.textProperty()); + + + + + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow4.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow4.java new file mode 100644 index 0000000..58a71d1 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/OtherWindow4.java @@ -0,0 +1,50 @@ +package fi.utu.tech.gui.javafx; + +import java.io.IOException; + +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.layout.StackPane; + + +public class OtherWindow4 extends BorderPane { + public OtherWindow4() { + + getStylesheets().add(getClass().getResource("styles2.css").toExternalForm()); + + Circle c = new Circle(200, Color.NAVY); + setLeft(new ImageView(ResourceLoader.image("hmm.png"))); + + Label terve = new Label("Terve"); + setAlignment(terve,Pos.CENTER); + setTop(terve); + + Label maailma = new Label("Maailma"); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(c, maailma); + setCenter(stackPane); + + TextField textField1 = new TextField(); + TextField textField2 = new TextField(); + TextField textField3 = new TextField(); + HBox hBox = new HBox(textField1,textField2,textField3); + setBottom(hBox); + + + + + + + + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java new file mode 100644 index 0000000..f917bd3 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java @@ -0,0 +1,42 @@ +package fi.utu.tech.gui.javafx; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; + +public class ResourceLoader<N extends Parent, C> { + protected final N root; + protected final C controller; + + public ResourceLoader(String contentPath) { + N root_ = null; + C controller_ = null; + try { + // determines where to look for the resources (the root path) + Class resourceRootClass = getClass(); + + FXMLLoader loader = new FXMLLoader(resourceRootClass.getResource(contentPath)); + root_ = loader.load(); + controller_ = loader.getController(); + System.out.println("DEBUG: " + contentPath + " loaded."); + } catch (Exception e) { + Dialogs.warning( + "Internal error", + "Could not open FXML file: " + contentPath, + "Reason: " + e.getMessage() + ); + System.exit(1); + } + root = root_; + controller = controller_; + } + + // finds images both outside and inside jars + public static String image(String fileName) { + return ResourceLoader.class.getResource(fileName).toExternalForm(); + } + + // finds stylesheets both outside and inside jars + public static String stylesheet(String fileName) { + return ResourceLoader.class.getResource(fileName).toExternalForm(); + } +} diff --git a/Project 1/src/main/java/fi/utu/tech/gui/javafx/Teht_5_ikkuna.java b/Project 1/src/main/java/fi/utu/tech/gui/javafx/Teht_5_ikkuna.java new file mode 100644 index 0000000..30402f3 --- /dev/null +++ b/Project 1/src/main/java/fi/utu/tech/gui/javafx/Teht_5_ikkuna.java @@ -0,0 +1,80 @@ +package fi.utu.tech.gui.javafx; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.text.Font; + +public class Teht_5_ikkuna extends BorderPane{ + + Label label2; + + public Teht_5_ikkuna(){ + + setLeft(new ImageView(ResourceLoader.image("hmm.png"))); + + FXMLController2 ctrl = new FXMLController2(); + BorderPane bp = new BorderPane(); + setAlignment(bp, Pos.CENTER); + bp.setPadding(new Insets(16, 16, 16, 16)); + //setPrefHeight(bp); + //setMargin(bp, new Insets(16.0, 16.0, 16.0, 16.0)); + bp.setTop(new Label("What should we do?")); + Button button1 = new Button("Open a new window"); + Button button2 = new Button("Display Dialog"); + Button button3 = new Button("Exit"); + Label label1 = new Label("Loading..."); + + ctrl.setLabel(label1); + label2 = new Label("Welcome!"); + + EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() { + public void handle(ActionEvent e) + { + ctrl.handleWindowButton(e); + } + }; + EventHandler<ActionEvent> event2 = new EventHandler<ActionEvent>() { + public void handle(ActionEvent e) + { + ctrl.handleDialogButton(e); + } + }; + EventHandler<ActionEvent> event3 = new EventHandler<ActionEvent>() { + public void handle(ActionEvent e) + { + ctrl.handleExitButton(e); + } + }; + button1.setOnAction(event); + button2.setOnAction(event2); + button3.setOnAction(event3); + + button1.setPadding(new Insets(5, 110, 5, 110)); + button2.setPadding(new Insets(5, 122, 5, 122)); + button3.setPadding(new Insets(5, 154, 5, 154)); + + VBox vbox = new VBox(button1, button2, button3, label1); + vbox.setAlignment(Pos.CENTER); + vbox.setPadding(new Insets(16, 16, 16, 16)); + + Label otsikko = new Label("What should we do?"); + bp.setTop(otsikko); + bp.setAlignment(otsikko, Pos.CENTER); + bp.setMargin(otsikko,new Insets(15, 0, 15, 0)); + otsikko.setFont(new Font(28)); + + bp.setCenter(vbox); + setAlignment(bp ,Pos.CENTER); + bp.setMargin(vbox,new Insets(5, 40, 5, 40)); + + setCenter(bp); + } + +} \ No newline at end of file diff --git a/Project 1/src/main/resources/fi/utu/tech/gui/javafx/hmm.png b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/hmm.png new file mode 100644 index 0000000000000000000000000000000000000000..25e8eb650f6752e716570cfb3c590bf09dff41ee GIT binary patch literal 9965 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 literal 0 HcmV?d00001 diff --git a/Project 1/src/main/resources/fi/utu/tech/gui/javafx/otherwindow.fxml b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/otherwindow.fxml new file mode 100644 index 0000000..cfaf34b --- /dev/null +++ b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/otherwindow.fxml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.StackPane?> +<?import javafx.scene.shape.Circle?> + + +<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="550.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fi.utu.tech.gui.javafx.FXMLController2"> + <left> + <ImageView fitHeight="379.0" fitWidth="163.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@hmm.png" /> + </image> + <BorderPane.margin> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </BorderPane.margin> + </ImageView> + </left> + <top> + <Label text="Terve" BorderPane.alignment="CENTER" /> + </top> + <center> + <StackPane prefHeight="358.0" prefWidth="388.0" BorderPane.alignment="CENTER"> + <children> + <Circle fill="#0e166f" radius="178.0" smooth="false" stroke="BLACK" strokeType="INSIDE" /> + <Label text="Maailma" /> + </children> + </StackPane> + </center> + <bottom> + <HBox alignment="BOTTOM_RIGHT" prefHeight="14.0" prefWidth="600.0" BorderPane.alignment="CENTER"> + <children> + <TextField prefHeight="25.0" prefWidth="103.0" /> + <TextField prefHeight="25.0" prefWidth="108.0" /> + <TextField prefHeight="25.0" prefWidth="113.0" /> + </children> + </HBox> + </bottom> +</BorderPane> diff --git a/Project 1/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml new file mode 100644 index 0000000..434e233 --- /dev/null +++ b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fi.utu.tech.gui.javafx.FXMLController"> + <left> + <ImageView fitHeight="368.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@hmm.png" /> + </image> + <BorderPane.margin> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </BorderPane.margin> + </ImageView> + </left> + <center> + <BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <padding> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </padding> + <center> + <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="16.0" BorderPane.alignment="CENTER"> + <padding> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </padding> + <children> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleWindowButton" text="Open a new window" /> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleDialogButton" text="Display a dialog" /> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleExitButton" text="Exit" /> + <Label fx:id="clicksLabel" text="Loading..." /> + </children></VBox> + </center> + <top> + <Label text="What should we do?" BorderPane.alignment="CENTER"> + <font> + <Font size="26.0" /> + </font></Label> + </top> + </BorderPane> + </center> +</BorderPane> diff --git a/Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles.css b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles.css new file mode 100644 index 0000000..3ab643a --- /dev/null +++ b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles.css @@ -0,0 +1,3 @@ +.button { + -fx-font-weight: bold; +} diff --git a/Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles2.css b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles2.css new file mode 100644 index 0000000..6444aaf --- /dev/null +++ b/Project 1/src/main/resources/fi/utu/tech/gui/javafx/styles2.css @@ -0,0 +1,7 @@ +.root { + -fx-font-weight: bold; + -fx-background-color: black; +} +.root .label { + -fx-text-fill: white; +} diff --git a/Project 2/.gitignore b/Project 2/.gitignore new file mode 100644 index 0000000..2b18655 --- /dev/null +++ b/Project 2/.gitignore @@ -0,0 +1,21 @@ +bin/ +contracts/ +.classpath +.project +.history/ +.idea +.jqwik-database +.lib/ +.worksheet +.settings/ +*.iml +*.ipr +*.iws +*.log +project/boot/ +project/plugins/project/ +project/project/ +project/*-shim.sbt +project/target/ +target/ +openjfx/ diff --git a/Project 2/.gitlab-ci.yml b/Project 2/.gitlab-ci.yml new file mode 100644 index 0000000..4b3553b --- /dev/null +++ b/Project 2/.gitlab-ci.yml @@ -0,0 +1,13 @@ +image: maven:latest + +stages: + - build + - test + +build: + script: + - mvn compile + +test: + script: + - mvn test \ No newline at end of file diff --git a/Project 2/README.md b/Project 2/README.md new file mode 100644 index 0000000..2907fd1 --- /dev/null +++ b/Project 2/README.md @@ -0,0 +1,39 @@ +# Project description + +Simple template for projects that make use of JavaFX and FXML (Scene Builder). +Requires Java 11 or later. Compatible with +Eclipse and IntelliJ IDEA. Minor issues with Netbeans. Automatically +integrates with Gitlab CI. + +## Installation + +Maven: + +```bash +$ git clone https://gitlab.utu.fi/tech/education/gui/template-javafx + +$ cd template-javafx + +$ mvn compile exec:java +``` + +SBT: + +```bash +$ git clone https://gitlab.utu.fi/tech/education/gui/template-javafx + +$ cd template-javafx + +$ sbt compile run +``` + +## Further instructions + + * Java platform: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/jvm-platform + * Maven: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/maven-misc + * SBT: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/sbt-misc + +External sources: + + * JavaFX: https://openjfx.io/javadoc/11/ + * Scene Builder: https://docs.gluonhq.com/scenebuilder/ diff --git a/Project 2/build.sbt b/Project 2/build.sbt new file mode 100644 index 0000000..e703e90 --- /dev/null +++ b/Project 2/build.sbt @@ -0,0 +1,236 @@ +// Project template + +// Supported operating systems: Windows, Mac, Linux +// Supported JDKs: 8, 10+ + +// Project name +name := "template-javafx" + +// organization name +organization := "fi.utu.tech" + +version := "1.0" + +// project description +description := "JavaFX project template" + +// main class +Compile/mainClass := Some("fi.utu.tech.gui.javafx.Main") + +// force the java version by typing it here (remove the comment) +val force_javaVersion = None // Some(13) + +// force the javafx version by typing it here (remove the comment) +val force_javaFxVersion = None // Some(13) + +val useJavaFX = true + +val useScalaOrScalaFX = true + +// END_OF_SIMPLE_CONFIGURATION +// you can copy the rest for each new project +// --- --- --- + +def fail(msg: String) = { + println("Error :-/") + println + println(msg) + System.exit(1) + null +} + +val detectedJDK = System.getProperty("java.version").replace("-ea","").split('.').dropWhile(_.toInt<8).head.toInt + +val javaVersionNum = force_javaVersion.getOrElse(detectedJDK) + +val javaVersionString = javaVersionNum match { + case 7 => "1.7" + case 8 => "1.8" + case x if x > 8 => x.toString +} + +val lts = 11 +val dev = 13 + +val supported = javaVersionNum match { + case x if x < 8 => fail("Your Java installation is obsolete. Please upgrade to Java " + lts + "LTS") + case 9 => fail("Your Java installation is unsupported and has known issues. Please upgrade to Java " + lts + "LTS") + case x if x < lts => println("Consider upgrading to Java " + lts + " LTS"); true + case x if x > lts && x < dev => println("Consider upgrading to Java " + dev); true + case x if x > dev => println("Unsupported early access version. Consider switching back to Java " + dev); true + case _ => true +} + +javacOptions ++= Seq("-source", javaVersionString, "-target", javaVersionString, "-encoding", "utf8", "-Xlint:unchecked", "-Xlint:deprecation") +javacOptions in doc := Seq("-source", javaVersionString) + +enablePlugins(JShellPlugin) + +compileOrder := CompileOrder.JavaThenScala + +// Enables publishing to maven repo +publishMavenStyle := true + +// Do not append Scala versions to the generated artifacts +crossPaths := false + +// This forbids including Scala related libraries into the dependency +autoScalaLibrary := false + +assemblyMergeStrategy in assembly := { + case PathList("META-INF", xs @ _*) => MergeStrategy.discard + case _ => MergeStrategy.first +} + +// contains libraries provided by utu/ft dep +resolvers += "ftdev" at "https://ftdev.utu.fi/maven2" + +fork in Global := true + +val javaVersion = taskKey[Unit]("Prints the Java version.") + +javaVersion := { println("SBT uses Java SDK located at "+System.getProperty("java.home")) } + +publishTo := Some(Resolver.file("file", new File("/tmp/repository"))) + +val oomkit = "fi.utu.tech" % "oomkit" % "1.15" + +libraryDependencies ++= Seq() + +//// +//// JQWIK / JUNIT configuration +//// + +resolvers in ThisBuild += Resolver.jcenterRepo + +val junit_version = "5.5.2" + +// library dependencies. (orginization name) % (project name) % (version) +libraryDependencies ++= Seq( + "net.aichler" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test, + "org.junit.platform" % "junit-platform-commons" % ("1"+junit_version.tail) % Test, + "org.junit.platform" % "junit-platform-runner" % ("1"+junit_version.tail) % Test, + "org.junit.jupiter" % "junit-jupiter-engine" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-api" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-migrationsupport" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-params" % junit_version % Test, + "net.jqwik" % "jqwik" % "1.2.0" % Test, + "org.scalatest" %% "scalatest" % "3.0.8" % Test, +) + +testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-c") + +//// +//// JAVAFX configuration +//// + +val javafx_versions = if (!useJavaFX) (0,"-","-") else (force_javaFxVersion getOrElse javaVersionNum) match { + case 7 => (7, "7", "8.0.181-R13") + case 8 => (8, "8", "8.0.181-R13") + case 10 => (11, "11.0.2", "11-R16") + case x if x>10 => (13, "13", "12.0.2-R18") + case _ => fail("Unsupported Java version for JavaFX") +} + +// JAVA_HOME location +val javaHomeDir = { + val path = try { + if (scala.sys.env("JAVA_HOME").trim.isEmpty) throw new Exception("Empty JAVA_HOME") else scala.sys.env("JAVA_HOME") + } catch { + case _: Throwable => System.getProperty("java.home") // not set -> ask from current JVM + } + + val f = file(path) + if (!f.exists()) fail("The environment variable JAVA_HOME points to a non-existent directory!\nSolution: Edit your system settings (Windows control panel / *nix .bashrc) and fix the JAVA_HOME location.") + f +} + +val osName: SettingKey[String] = SettingKey[String]("osName") + +osName := (System.getProperty("os.name") match { + case n if n.startsWith("Linux") => "linux" + case n if n.startsWith("Mac") => "mac" + case n if n.startsWith("Windows") => "win" + case _ => throw new Exception("Unknown platform!") +}) + +def legacyJavaFX() = { + val searchDirs = Seq( + "/jre/lib/jfxrt.jar", // OpenJDK 7 + "/jre/lib/ext/jfxrt.jar", // OpenJDK 8 + "/lib/ext/jfxrt.jar" // Windows & Oracle Java 8 + ) + + if (detectedJDK > 8) fail(s"Trying to use legacy non-modular JavaFX with a modern JDK [$detectedJDK].\nSolution: Check the line 'val force_javaFxVersion =' in build.sbt.") + + val javaFxJAR = searchDirs.map{ searchDir => file(javaHomeDir + searchDir) }.find{ _.exists() } + + javaFxJAR.getOrElse { + fail(s"Java FX runtime not installed in [${javaHomeDir.toString}]!\nSolution: Install JavaFX or consider upgrading your JDK so that JavaFX can be installed automatically.") + } +} + +val jfx_sdk_version = javafx_versions._2 +val jfx_scalafx_version = javafx_versions._3 + +val javaFxPath = Def.taskKey[File]("OpenJFX fetcher") +javaFxPath := { + val javaFxHome = + try { + val envHome = file(scala.sys.env("JAVAFX_HOME")) + if (envHome.toString.trim.isEmpty) throw new Exception("Empty JAVAFX_HOME") + println("Using OpenJFX from " + envHome) + envHome + } + catch { case _: Throwable => + println("Using local OpenJFX") + baseDirectory.value / "openjfx" + } + + if (!javaFxHome.exists()) java.nio.file.Files.createDirectory(javaFxHome.toPath) + + val jfx_os = osName.value match { + case "linux" => "linux" + case "mac" => "osx" + case "win" => "windows" + } + + val sdkURL = "http://download2.gluonhq.com/openjfx/" + jfx_sdk_version + "/openjfx-" + jfx_sdk_version + "_" + jfx_os + "-x64_bin-sdk.zip" + + try { + val testDir = javaFxHome / "all.ok" + if (!testDir.exists()) { + println("Fetching OpenJFX from "+sdkURL+"..") + IO.unzipURL(new URL(sdkURL), javaFxHome) + java.nio.file.Files.createDirectory(testDir.toPath) + println("Fetching OpenJFX done.") + } else { + println("Using OpenJFX from "+javaFxHome) + } + + javaFxHome + } + catch { + case t: Throwable => fail("Could not load OpenJFX! Reason:" + t.getMessage) + } +} + +val jfxModules = Seq("base","controls","fxml","graphics","media","swing","web") + + +if (!useJavaFX) Seq() else javafx_versions._1 match { + case 7 => + // TODO libraryDependencies + Seq(unmanagedJars in Compile += Attributed.blank(legacyJavaFX())) + case 8 => + (if (useScalaOrScalaFX) Seq(libraryDependencies += "org.scalafx" %% "scalafx" % jfx_scalafx_version) else Seq()) ++ + Seq(unmanagedJars in Compile += Attributed.blank(legacyJavaFX())) + case _ => + Seq( + javaOptions in run ++= Seq( + "--module-path", (javaFxPath.value / ("javafx-sdk-" + jfx_sdk_version) / "lib").toString, + "--add-modules=" + jfxModules.map("javafx."+_).mkString(",")) + ) ++ + (if (useScalaOrScalaFX) Seq(libraryDependencies += "org.scalafx" % "scalafx_2.13" % jfx_scalafx_version) else Seq()) ++ + jfxModules.map(module => libraryDependencies += "org.openjfx" % ("javafx-"+module) % jfx_sdk_version classifier osName.value) +} diff --git a/Project 2/pom.xml b/Project 2/pom.xml new file mode 100644 index 0000000..40e1bb8 --- /dev/null +++ b/Project 2/pom.xml @@ -0,0 +1,452 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <!-- + + Welcome! + This pom.xml is *THE* file that defines your Maven style Java project. + + Eclipse, IDEA and other development environments with Maven support + or plugins can *import* this project by reading this file. + + It usually contains tons of barely readable configuration. Luckily + this basic pom.xml is somewhat readable. All necessary configuration + for customizing your project if located here on top before the + 'END OF SIMPLE CONFIGURATION' line below. + + Some basics: + + - Maven is a build system for Java/JVM + - the Maven projects define an artifact that has a three part id: + groupId - artifactId - version + + - For example, this project is called: + fi.utu.tech - oomkit - 1.15 + + - The group id is usually a web domain in reverse order. + + - You can use these id parts to search for projects at + https://search.maven.org/ + + - If your version is not final and you have plans to modify the + source code at some point, please use a version id that ends with + -SNAPSHOT, e.g 1.0-SNAPSHOT. Maven may cache the project jar + and refuse to overwrite old cached versions with new ones unless + you remember this convention. + + - This file supports + compiling the project with 'mvn compile' (see target/) + cleaning the class files with 'mvn clean' + executing the main routine with 'mvn exec:java' + executing the unit tests with 'mvn test' + packaging the application with 'mvn package' (see target/) + + - the Maven project structure is as follows: + https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html + + --> + + <!-- ==== START OF SIMPLE CONFIGURATION ==== --> + + <!-- the three parts of the artifact name --> + <groupId>fi.utu.tech</groupId> + <artifactId>template-javafx</artifactId> + <version>1.0</version> + + <!-- additional information about the project --> + <name>JavaFX project template</name> + <url>https://gitlab.utu.fi/tech/education/gui/template-javafx</url> + + <packaging>jar</packaging> + + <!-- HINT: More configuration here! --> + <properties> + <!-- Configures this project to use 'fi.utu.tech.AppMain' as its main class --> + <project.mainclass>fi.utu.tech.gui.javafx.Main</project.mainclass> + + + <!-- Don't touch these unless you know what you're doing! + + For example, the source encoding should always be utf-8. + You're probably doing something stupid if you think it + should be a 8-bit code page in 2019. --> + <jdk.version>11</jdk.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <jqwik.version>1.2.0</jqwik.version> + <junit.version>5.5.2</junit.version> + <junitplatform.version>1.5.2</junitplatform.version> + <javafx.version>13</javafx.version> + </properties> + + <!-- ==== END OF SIMPLE CONFIGURATION ==== --> + + <repositories> + <repository> + <snapshots> + <enabled>false</enabled> + </snapshots> + <id>central</id> + <name>Central Repository</name> + <url>https://repo.maven.apache.org/maven2</url> + </repository> + <repository> + <id>jcenter</id> + <name>jcenter</name> + <url>https://jcenter.bintray.com/</url> + </repository> + <!-- UTU repository --> + <repository> + <id>ftdev</id> + <name>ftdev</name> + <url>https://ftdev.utu.fi/maven2</url> + </repository> + </repositories> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-commons</artifactId> + <version>${junitplatform.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>net.jqwik</groupId> + <artifactId>jqwik</artifactId> + <version>${jqwik.version}</version> + <scope>test</scope> + </dependency><!-- + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-migrationsupport</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-runner</artifactId> + <version>${junitplatform.version}</version> + <scope>test</scope> + </dependency> --> + + <!-- JavaFX (remove if not needed to speed up dep downloads)--> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-base</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-fxml</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-media</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-swing</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-web</artifactId> + <version>${javafx.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Make a 'fat' jar, that is, jar that contains all its dependencies and runs as is. + See: https://stackoverflow.com/a/57691362 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>module-info.class</exclude> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>${project.mainclass}</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + + <!-- Run this app with exec:java --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.6.0</version> + <executions> + <execution> + <goals> + <goal>java</goal> + </goals> + </execution> + </executions> + <configuration> + <mainClass>${project.mainclass}</mainClass> + <arguments> + <argument>arg1</argument> + <argument>arg2</argument> + </arguments> + </configuration> + </plugin> + + <!-- Make the packaged jar executable --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.1.2</version> + <configuration> + <!-- DO NOT include log4j.properties file in your Jar --> + <excludes> + <exclude>**/log4j.properties</exclude> + </excludes> + <archive> + <manifest> + <!-- Jar file entry point --> + <mainClass>${project.mainclass}</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + + <!-- download source code in Eclipse, best practice --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + <version>2.10</version> + <configuration> + <downloadSources>true</downloadSources> + <downloadJavadocs>false</downloadJavadocs> + </configuration> + </plugin> + + <!-- JDK source/target versions --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + <configuration> + <source>${jdk.version}</source> + <target>${jdk.version}</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>3.1.1</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + <configuration> + <tags> + <tag> + <name>toDo</name> + <placement>a</placement> + <head>To do:</head> + </tag> + <tag> + <name>classInvariant</name> + <placement>t</placement> + <head>Class invariant:</head> + </tag> + <tag> + <name>classInvariantProtected</name> + <placement>t</placement> + <head>Protected class invariant:</head> + </tag> + <tag> + <name>classInvariantPrivate</name> + <placement>t</placement> + <head>Private class invariant:</head> + </tag> + <tag> + <name>abstractionFunction</name> + <placement>t</placement> + <head>Abstraction function:</head> + </tag> + <tag> + <name>pre</name> + <placement>cm</placement> + <head>Precondition:</head> + </tag> + <tag> + <name>post</name> + <placement>cm</placement> + <head>Postcondition:</head> + </tag> + <tag> + <name>postProtected</name> + <placement>cm</placement> + <head>Protected postcondition:</head> + </tag> + <tag> + <name>postPrivate</name> + <placement>cm</placement> + <head>Private postcondition:</head> + </tag> + <tag> + <name>time</name> + <placement>cmf</placement> + <head>Time complexity:</head> + </tag> + <tag> + <name>space</name> + <placement>cmf</placement> + <head>Space complexity:</head> + </tag> + <tag> + <name>correspondence</name> + <placement>a</placement> + <head>Correspondence:</head> + </tag> + <tag> + <name>download</name> + <placement>a</placement> + <head>Download:</head> + </tag> + </tags> + <show>protected</show> + <failOnError>false</failOnError> + <sourceFileExcludes> + <sourceFileExclude>**/module-info.java</sourceFileExclude> + </sourceFileExcludes> + </configuration> + </plugin> + + <!-- JUnit & JQwik test integration --> + + <!-- junit-platform-maven-plugin: supports modular tests + maven-surefire-plugin: non-modular tests + + Modular testing works via command line mvn, but is + still broken in Eclipse due to this + https://bugs.eclipse.org/bugs/show_bug.cgi?id=520667 + --> + <plugin> + <groupId>de.sormuras.junit</groupId> + <artifactId>junit-platform-maven-plugin</artifactId> + <version>1.0.0-M5</version> + <extensions>true</extensions> + <configuration> + <executor>JAVA</executor> + </configuration> + </plugin> + + <!-- + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> --> + + <!-- javafx:jlink: + https://github.com/openjfx/javafx-maven-plugin + Use 'mvn package' instead if you don't need jlink. + + <plugin> + <groupId>org.openjfx</groupId> + <artifactId>javafx-maven-plugin</artifactId> + <version>0.0.3</version> + <configuration> + <mainClass>${project.mainclass}</mainClass> + </configuration> + </plugin> --> + + </plugins> + <extensions> + <!-- Enables the use of SSH for deployments --> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh-external</artifactId> + <version>3.3.3</version> + </extension> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh</artifactId> + <version>3.3.3</version> + </extension> + </extensions> + </build> + <distributionManagement> + <repository> + <id>ftdev</id> + <name>UTU tech ftdev repository</name> + <url>scp://localhost:2222/var/www/maven2</url> + </repository> + </distributionManagement> +</project> diff --git a/Project 2/project/build.properties b/Project 2/project/build.properties new file mode 100644 index 0000000..6adcdc7 --- /dev/null +++ b/Project 2/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.3 diff --git a/Project 2/project/plugins.sbt b/Project 2/project/plugins.sbt new file mode 100644 index 0000000..37ca261 --- /dev/null +++ b/Project 2/project/plugins.sbt @@ -0,0 +1,7 @@ +resolvers += Resolver.jcenterRepo + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9") +addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.8.3") +addSbtPlugin("com.github.xuwei-k" % "sbt-jshell" % "0.1.2") +//addSbtPlugin("org.xerial.sbt" % "sbt-sql-sqlite" % "0.8") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.4.0") diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/BMI.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/BMI.java new file mode 100644 index 0000000..c6c72d3 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/BMI.java @@ -0,0 +1,46 @@ +package fi.utu.tech.gui.javafx; + +import javafx.event.EventHandler; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +public class BMI extends BorderPane{ + + public BMI() { + Label label1 = new Label("Pituus: "); + Label label2 = new Label("Paino: "); + Label tulos = new Label("Virhellinen syöttö"); + Label BMI = new Label("BMI: "); + TextField tf1 = new TextField(); + TextField tf2 = new TextField(); + + VBox vb1 = new VBox(label1, tf1); + VBox vb2 = new VBox(label2, tf2); + + HBox hb = new HBox(vb1, vb2); + HBox hb2 = new HBox(BMI, tulos); + + EventHandler<KeyEvent> BMIKaava = e -> { + try { + float pituus = Float.valueOf(tf1.getText()); + float paino = Float.valueOf(tf2.getText()); + tulos.setText(String.valueOf((paino/(pituus*pituus)))); + } catch(Exception a) { + tulos.setText("Virheellinen syöttö"); + } + }; + + + tf1.setOnKeyTyped(BMIKaava); + tf2.setOnKeyTyped(BMIKaava); + + + setCenter(hb); + setBottom(hb2); + + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/CanvasWindow.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/CanvasWindow.java new file mode 100644 index 0000000..c01c54d --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/CanvasWindow.java @@ -0,0 +1,84 @@ +package fi.utu.tech.gui.javafx; + +import java.util.Random; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; + +public class CanvasWindow extends BorderPane{ + + public CanvasWindow() { + + Canvas canvas = new Canvas(500, 500); + final GraphicsContext graphicsContext = canvas.getGraphicsContext2D(); + initDraw(graphicsContext); + Random r = new Random(); + + + canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>(){ + + @Override + public void handle(MouseEvent event) { + if (event.getButton() == MouseButton.PRIMARY) { + graphicsContext.beginPath(); + graphicsContext.moveTo(event.getX(), event.getY()); + graphicsContext.stroke(); + } else if (event.getButton() == MouseButton.SECONDARY) { + + } + } + }); + + canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>(){ + + @Override + public void handle(MouseEvent event) { + if (event.getButton() == MouseButton.PRIMARY) { + graphicsContext.lineTo(event.getX(), event.getY()); + graphicsContext.stroke(); + } else if (event.getButton() == MouseButton.SECONDARY) { + graphicsContext.fillRect(event.getX()+ (r.nextInt(20)-10), event.getY()+ (r.nextInt(20)-10), 2, 2); + graphicsContext.stroke(); + } + } + }); + + canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>(){ + + @Override + public void handle(MouseEvent event) { + + } + }); + + setCenter(canvas); + } + + private void initDraw(GraphicsContext gc){ + double canvasWidth = gc.getCanvas().getWidth(); + double canvasHeight = gc.getCanvas().getHeight(); + + gc.setFill(Color.BLACK); + gc.setStroke(Color.BLACK); + gc.setLineWidth(5); + + gc.fill(); + gc.strokeRect( + 0, //x of the upper left corner + 0, //y of the upper left corner + canvasWidth, //width of the rectangle + canvasHeight); //height of the rectangle + + gc.setFill(Color.BLACK); + gc.setStroke(Color.BLACK); + gc.setLineWidth(1); + + } + +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java new file mode 100644 index 0000000..a4d0180 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java @@ -0,0 +1,13 @@ +package fi.utu.tech.gui.javafx; + +import javafx.scene.control.Alert; + +public class Dialogs { + public static void warning(String title, String header, String content) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + } +} \ No newline at end of file diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java new file mode 100644 index 0000000..7777d66 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/FXMLController.java @@ -0,0 +1,63 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Platform; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.stage.Stage; + +import java.util.function.Supplier; + +public class FXMLController { + private IntegerProperty clicks = new SimpleIntegerProperty(0); + + private Supplier<Parent> supplier; + + @FXML + private Label clicksLabel; + + protected void updateClicks() { + if (!clicksLabel.textProperty().isBound()) + clicksLabel.textProperty().bind(clicks.asString().concat(" clicks.")); + + clicks.setValue(clicks.getValue() + 1); + } + + protected void setLabel(String text) { + if (!clicksLabel.textProperty().isBound()) + clicksLabel.setText(text); + } + + @FXML + protected void handleDialogButton(ActionEvent event) { + updateClicks(); + Dialogs.warning("Example dialog", "Content header", "Content"); + } + + @FXML + protected void handleExitButton(ActionEvent event) { + System.out.println("Closing app."); + Platform.exit(); + } + + @FXML + protected void handleWindowButton(ActionEvent event) { + updateClicks(); + Scene other = new Scene(supplier.get()); + Stage otherStage = new Stage(); + otherStage.setScene(other); + otherStage.show(); + } + + protected void setWindowFactory(Supplier<Parent> supplier) { + this.supplier = supplier; + } + + public void initialize() { + System.out.println("2-A"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/Main.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/Main.java new file mode 100644 index 0000000..8039f74 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/Main.java @@ -0,0 +1,29 @@ +package fi.utu.tech.gui.javafx; + +public class Main { + /** + * The main() method is ignored in correctly deployed JavaFX application. + * main() serves only as fallback in case the application can not be + * launched through deployment artifacts, e.g., in IDEs with limited FX + * support. NetBeans ignores main(). + * + * @param args the command line arguments + */ + public static void main(String[] args) { + System.out.println("Stage A"); + if (args.length == 1 && args[0].equals("--test")) return; + + int exercise = 1; + if (args.length == 1) exercise = Integer.parseInt(args[0]); + switch(exercise) { + case 1: MainApp1.launch(MainApp1.class, args); break; + case 2: MainApp2.launch(MainApp2.class, args); break; + case 3: MainApp3.launch(MainApp3.class, args); break; + case 4: MainApp4.launch(MainApp4.class, args); break; + case 5: MainApp5.launch(MainApp5.class, args); break; + case 6: MainApp6.launch(MainApp6.class, args); break; + default: MainApp.launch(MainApp.class, args); break; + } + } + +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp.java new file mode 100644 index 0000000..6dc8ba3 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new OtherWindow(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp1.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp1.java new file mode 100644 index 0000000..5b8c8b3 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp1.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp1 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new PasswordWindow(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp2.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp2.java new file mode 100644 index 0000000..f86a01d --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp2.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp2 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new BMI(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp3.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp3.java new file mode 100644 index 0000000..58ea02f --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp3.java @@ -0,0 +1,76 @@ +package fi.utu.tech.gui.javafx; + +import javafx.animation.Animation; +import javafx.animation.Interpolator; +import javafx.animation.RotateTransition; +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.util.Duration; + +public class MainApp3 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new OtherWindow(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + + RotateTransition rt = new RotateTransition(Duration.millis(5000), loader.root); + + rt.setCycleCount(Animation.INDEFINITE); + rt.setFromAngle(0); + rt.setToAngle(360); + rt.setInterpolator(Interpolator.LINEAR); + rt.play(); + + loader.root.setOnMouseClicked(mouseEvent -> { + rt.setRate(-1*rt.getRate()); + }); + + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java new file mode 100644 index 0000000..253aaae --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp4.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp4 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new WebSelain(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java new file mode 100644 index 0000000..73acf69 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp5.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp5 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new StackPaneWindow(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp6.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp6.java new file mode 100644 index 0000000..fefcf2d --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/MainApp6.java @@ -0,0 +1,59 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp6 extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + System.out.println("Stage B"); + } + + @Override + public void stop() { + System.out.println("Stage E"); + } + + protected Parent createWindow() { + return new CanvasWindow(); + } + + protected String createStyle() { + return ResourceLoader.stylesheet("styles.css"); + } + + @Override + public void start(Stage stage) throws Exception { + System.out.println("Stage C"); + ResourceLoader<Parent, FXMLController> loader = new ResourceLoader<>("scene.fxml"); + + loader.controller.setLabel("Welcome!"); + + // configure the menu to create windows with createWindow + loader.controller.setWindowFactory(this::createWindow); + + Scene scene = new Scene(loader.root); + scene.getStylesheets().add(createStyle()); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + System.out.println("Stage D"); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java new file mode 100644 index 0000000..5328b15 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/OtherWindow.java @@ -0,0 +1,14 @@ +package fi.utu.tech.gui.javafx; + +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; + +public class OtherWindow extends BorderPane { + public OtherWindow() { + Circle c = new Circle(200, Color.NAVY); + setCenter(c); + setLeft(new ImageView(ResourceLoader.image("hmm.png"))); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/PasswordWindow.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/PasswordWindow.java new file mode 100644 index 0000000..9c79998 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/PasswordWindow.java @@ -0,0 +1,91 @@ +package fi.utu.tech.gui.javafx; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Formatter; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; + +public class PasswordWindow extends BorderPane { + + public PasswordWindow() { + PasswordField pf = new PasswordField(); + Label label = new Label("Aseta salasana"); + Button b1 = new Button("Tyhjennä"); + Button b2 = new Button("Hyväksy"); + HBox hb = new HBox(b1, b2); + Alert alert = new Alert(Alert.AlertType.NONE); + + + EventHandler<KeyEvent> salasanaPituus = e -> { + if(pf.getLength() < 8) { + label.setText("Salasana liian lyhyt"); + }else { + label.setText("Salasana on riittävän pitkä"); + } + }; + + + EventHandler<ActionEvent> tyhjennys = e -> { + pf.clear(); + label.setText("Aseta salasana"); + }; + + EventHandler<ActionEvent> hyvaksy = e -> { + Dialogs.warning("SHA1", "Salasana SHA1 muodossa", encryptPassword(pf.getText()) ); + }; + + pf.setOnKeyTyped(salasanaPituus); + b1.setOnAction(tyhjennys); + b2.setOnAction(hyvaksy); + + setTop(label); + setCenter(pf); + setBottom(hb); + + } + + + private static String encryptPassword(String password) + { + String sha1 = ""; + try + { + MessageDigest crypt = MessageDigest.getInstance("SHA-1"); + crypt.reset(); + crypt.update(password.getBytes("UTF-8")); + sha1 = byteToHex(crypt.digest()); + } + catch(NoSuchAlgorithmException e) + { + e.printStackTrace(); + } + catch(UnsupportedEncodingException e) + { + e.printStackTrace(); + } + return sha1; + } + + private static String byteToHex(final byte[] hash) + { + Formatter formatter = new Formatter(); + for (byte b : hash) + { + formatter.format("%02x", b); + } + String result = formatter.toString(); + formatter.close(); + return result; + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java new file mode 100644 index 0000000..f917bd3 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java @@ -0,0 +1,42 @@ +package fi.utu.tech.gui.javafx; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; + +public class ResourceLoader<N extends Parent, C> { + protected final N root; + protected final C controller; + + public ResourceLoader(String contentPath) { + N root_ = null; + C controller_ = null; + try { + // determines where to look for the resources (the root path) + Class resourceRootClass = getClass(); + + FXMLLoader loader = new FXMLLoader(resourceRootClass.getResource(contentPath)); + root_ = loader.load(); + controller_ = loader.getController(); + System.out.println("DEBUG: " + contentPath + " loaded."); + } catch (Exception e) { + Dialogs.warning( + "Internal error", + "Could not open FXML file: " + contentPath, + "Reason: " + e.getMessage() + ); + System.exit(1); + } + root = root_; + controller = controller_; + } + + // finds images both outside and inside jars + public static String image(String fileName) { + return ResourceLoader.class.getResource(fileName).toExternalForm(); + } + + // finds stylesheets both outside and inside jars + public static String stylesheet(String fileName) { + return ResourceLoader.class.getResource(fileName).toExternalForm(); + } +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/StackPaneWindow.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/StackPaneWindow.java new file mode 100644 index 0000000..a0cadac --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/StackPaneWindow.java @@ -0,0 +1,41 @@ +package fi.utu.tech.gui.javafx; + +import javafx.event.EventHandler; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Paint; +import javafx.scene.shape.Rectangle; + +public class StackPaneWindow extends BorderPane { + + public StackPaneWindow() { + + Rectangle r = new Rectangle(300,300, Paint.valueOf("#ff0000")); + + StackPane sp1 = new StackPane(r); + StackPane sp2 = new StackPane(sp1); + + EventHandler<MouseEvent> rKlikkaus = e -> { + System.out.println(r.toString()); + e.consume(); + }; + + EventHandler<MouseEvent> sp1Klikkaus = e -> { + System.out.println(sp1.toString()); + }; + + EventHandler<MouseEvent> sp2Klikkaus = e -> { + System.out.println(sp2.toString()); + }; + + r.setOnMouseClicked(rKlikkaus); + sp1.setOnMouseClicked(sp1Klikkaus); + sp2.setOnMouseClicked(sp2Klikkaus); + + setCenter(sp2); + + } + +} diff --git a/Project 2/src/main/java/fi/utu/tech/gui/javafx/WebSelain.java b/Project 2/src/main/java/fi/utu/tech/gui/javafx/WebSelain.java new file mode 100644 index 0000000..ae0bb79 --- /dev/null +++ b/Project 2/src/main/java/fi/utu/tech/gui/javafx/WebSelain.java @@ -0,0 +1,40 @@ +package fi.utu.tech.gui.javafx; + +import javafx.event.EventHandler; +import javafx.scene.Scene; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Region; +import javafx.scene.paint.Color; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; + +public class WebSelain extends BorderPane { + + public WebSelain() { + + TextField osoiteRivi = new TextField(); + + WebView webView = new WebView(); + setCenter(webView); + + setTop(osoiteRivi); + + osoiteRivi.setOnKeyPressed(new EventHandler<KeyEvent>(){ + @Override + public void handle(KeyEvent ke) + { + if (ke.getCode().equals(KeyCode.ENTER)) + { + System.out.println("moi"); + + webView.getEngine().load(osoiteRivi.getText()); + + } + } + }); + + } +} diff --git a/Project 2/src/main/resources/fi/utu/tech/gui/javafx/hmm.png b/Project 2/src/main/resources/fi/utu/tech/gui/javafx/hmm.png new file mode 100644 index 0000000000000000000000000000000000000000..25e8eb650f6752e716570cfb3c590bf09dff41ee GIT binary patch literal 9965 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 literal 0 HcmV?d00001 diff --git a/Project 2/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml b/Project 2/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml new file mode 100644 index 0000000..434e233 --- /dev/null +++ b/Project 2/src/main/resources/fi/utu/tech/gui/javafx/scene.fxml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fi.utu.tech.gui.javafx.FXMLController"> + <left> + <ImageView fitHeight="368.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@hmm.png" /> + </image> + <BorderPane.margin> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </BorderPane.margin> + </ImageView> + </left> + <center> + <BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <padding> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </padding> + <center> + <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="16.0" BorderPane.alignment="CENTER"> + <padding> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </padding> + <children> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleWindowButton" text="Open a new window" /> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleDialogButton" text="Display a dialog" /> + <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleExitButton" text="Exit" /> + <Label fx:id="clicksLabel" text="Loading..." /> + </children></VBox> + </center> + <top> + <Label text="What should we do?" BorderPane.alignment="CENTER"> + <font> + <Font size="26.0" /> + </font></Label> + </top> + </BorderPane> + </center> +</BorderPane> diff --git a/Project 2/src/main/resources/fi/utu/tech/gui/javafx/styles.css b/Project 2/src/main/resources/fi/utu/tech/gui/javafx/styles.css new file mode 100644 index 0000000..3ab643a --- /dev/null +++ b/Project 2/src/main/resources/fi/utu/tech/gui/javafx/styles.css @@ -0,0 +1,3 @@ +.button { + -fx-font-weight: bold; +} diff --git a/Project 3/.gitignore b/Project 3/.gitignore new file mode 100644 index 0000000..5bfb48e --- /dev/null +++ b/Project 3/.gitignore @@ -0,0 +1,20 @@ +contracts/ +.classpath +.project +.history/ +.idea +.jqwik-database +.lib/ +.worksheet +.settings/ +*.iml +*.ipr +*.iws +*.log +project/boot/ +project/plugins/project/ +project/project/ +project/*-shim.sbt +project/target/ +target/ +openjfx/ diff --git a/Project 3/.gitlab-ci.yml b/Project 3/.gitlab-ci.yml new file mode 100644 index 0000000..4b3553b --- /dev/null +++ b/Project 3/.gitlab-ci.yml @@ -0,0 +1,13 @@ +image: maven:latest + +stages: + - build + - test + +build: + script: + - mvn compile + +test: + script: + - mvn test \ No newline at end of file diff --git a/Project 3/README.md b/Project 3/README.md new file mode 100644 index 0000000..2907fd1 --- /dev/null +++ b/Project 3/README.md @@ -0,0 +1,39 @@ +# Project description + +Simple template for projects that make use of JavaFX and FXML (Scene Builder). +Requires Java 11 or later. Compatible with +Eclipse and IntelliJ IDEA. Minor issues with Netbeans. Automatically +integrates with Gitlab CI. + +## Installation + +Maven: + +```bash +$ git clone https://gitlab.utu.fi/tech/education/gui/template-javafx + +$ cd template-javafx + +$ mvn compile exec:java +``` + +SBT: + +```bash +$ git clone https://gitlab.utu.fi/tech/education/gui/template-javafx + +$ cd template-javafx + +$ sbt compile run +``` + +## Further instructions + + * Java platform: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/jvm-platform + * Maven: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/maven-misc + * SBT: https://gitlab.utu.fi/soft/ftdev/wikis/tutorials/sbt-misc + +External sources: + + * JavaFX: https://openjfx.io/javadoc/11/ + * Scene Builder: https://docs.gluonhq.com/scenebuilder/ diff --git a/Project 3/build.sbt b/Project 3/build.sbt new file mode 100644 index 0000000..e703e90 --- /dev/null +++ b/Project 3/build.sbt @@ -0,0 +1,236 @@ +// Project template + +// Supported operating systems: Windows, Mac, Linux +// Supported JDKs: 8, 10+ + +// Project name +name := "template-javafx" + +// organization name +organization := "fi.utu.tech" + +version := "1.0" + +// project description +description := "JavaFX project template" + +// main class +Compile/mainClass := Some("fi.utu.tech.gui.javafx.Main") + +// force the java version by typing it here (remove the comment) +val force_javaVersion = None // Some(13) + +// force the javafx version by typing it here (remove the comment) +val force_javaFxVersion = None // Some(13) + +val useJavaFX = true + +val useScalaOrScalaFX = true + +// END_OF_SIMPLE_CONFIGURATION +// you can copy the rest for each new project +// --- --- --- + +def fail(msg: String) = { + println("Error :-/") + println + println(msg) + System.exit(1) + null +} + +val detectedJDK = System.getProperty("java.version").replace("-ea","").split('.').dropWhile(_.toInt<8).head.toInt + +val javaVersionNum = force_javaVersion.getOrElse(detectedJDK) + +val javaVersionString = javaVersionNum match { + case 7 => "1.7" + case 8 => "1.8" + case x if x > 8 => x.toString +} + +val lts = 11 +val dev = 13 + +val supported = javaVersionNum match { + case x if x < 8 => fail("Your Java installation is obsolete. Please upgrade to Java " + lts + "LTS") + case 9 => fail("Your Java installation is unsupported and has known issues. Please upgrade to Java " + lts + "LTS") + case x if x < lts => println("Consider upgrading to Java " + lts + " LTS"); true + case x if x > lts && x < dev => println("Consider upgrading to Java " + dev); true + case x if x > dev => println("Unsupported early access version. Consider switching back to Java " + dev); true + case _ => true +} + +javacOptions ++= Seq("-source", javaVersionString, "-target", javaVersionString, "-encoding", "utf8", "-Xlint:unchecked", "-Xlint:deprecation") +javacOptions in doc := Seq("-source", javaVersionString) + +enablePlugins(JShellPlugin) + +compileOrder := CompileOrder.JavaThenScala + +// Enables publishing to maven repo +publishMavenStyle := true + +// Do not append Scala versions to the generated artifacts +crossPaths := false + +// This forbids including Scala related libraries into the dependency +autoScalaLibrary := false + +assemblyMergeStrategy in assembly := { + case PathList("META-INF", xs @ _*) => MergeStrategy.discard + case _ => MergeStrategy.first +} + +// contains libraries provided by utu/ft dep +resolvers += "ftdev" at "https://ftdev.utu.fi/maven2" + +fork in Global := true + +val javaVersion = taskKey[Unit]("Prints the Java version.") + +javaVersion := { println("SBT uses Java SDK located at "+System.getProperty("java.home")) } + +publishTo := Some(Resolver.file("file", new File("/tmp/repository"))) + +val oomkit = "fi.utu.tech" % "oomkit" % "1.15" + +libraryDependencies ++= Seq() + +//// +//// JQWIK / JUNIT configuration +//// + +resolvers in ThisBuild += Resolver.jcenterRepo + +val junit_version = "5.5.2" + +// library dependencies. (orginization name) % (project name) % (version) +libraryDependencies ++= Seq( + "net.aichler" % "jupiter-interface" % JupiterKeys.jupiterVersion.value % Test, + "org.junit.platform" % "junit-platform-commons" % ("1"+junit_version.tail) % Test, + "org.junit.platform" % "junit-platform-runner" % ("1"+junit_version.tail) % Test, + "org.junit.jupiter" % "junit-jupiter-engine" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-api" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-migrationsupport" % junit_version % Test, + "org.junit.jupiter" % "junit-jupiter-params" % junit_version % Test, + "net.jqwik" % "jqwik" % "1.2.0" % Test, + "org.scalatest" %% "scalatest" % "3.0.8" % Test, +) + +testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-c") + +//// +//// JAVAFX configuration +//// + +val javafx_versions = if (!useJavaFX) (0,"-","-") else (force_javaFxVersion getOrElse javaVersionNum) match { + case 7 => (7, "7", "8.0.181-R13") + case 8 => (8, "8", "8.0.181-R13") + case 10 => (11, "11.0.2", "11-R16") + case x if x>10 => (13, "13", "12.0.2-R18") + case _ => fail("Unsupported Java version for JavaFX") +} + +// JAVA_HOME location +val javaHomeDir = { + val path = try { + if (scala.sys.env("JAVA_HOME").trim.isEmpty) throw new Exception("Empty JAVA_HOME") else scala.sys.env("JAVA_HOME") + } catch { + case _: Throwable => System.getProperty("java.home") // not set -> ask from current JVM + } + + val f = file(path) + if (!f.exists()) fail("The environment variable JAVA_HOME points to a non-existent directory!\nSolution: Edit your system settings (Windows control panel / *nix .bashrc) and fix the JAVA_HOME location.") + f +} + +val osName: SettingKey[String] = SettingKey[String]("osName") + +osName := (System.getProperty("os.name") match { + case n if n.startsWith("Linux") => "linux" + case n if n.startsWith("Mac") => "mac" + case n if n.startsWith("Windows") => "win" + case _ => throw new Exception("Unknown platform!") +}) + +def legacyJavaFX() = { + val searchDirs = Seq( + "/jre/lib/jfxrt.jar", // OpenJDK 7 + "/jre/lib/ext/jfxrt.jar", // OpenJDK 8 + "/lib/ext/jfxrt.jar" // Windows & Oracle Java 8 + ) + + if (detectedJDK > 8) fail(s"Trying to use legacy non-modular JavaFX with a modern JDK [$detectedJDK].\nSolution: Check the line 'val force_javaFxVersion =' in build.sbt.") + + val javaFxJAR = searchDirs.map{ searchDir => file(javaHomeDir + searchDir) }.find{ _.exists() } + + javaFxJAR.getOrElse { + fail(s"Java FX runtime not installed in [${javaHomeDir.toString}]!\nSolution: Install JavaFX or consider upgrading your JDK so that JavaFX can be installed automatically.") + } +} + +val jfx_sdk_version = javafx_versions._2 +val jfx_scalafx_version = javafx_versions._3 + +val javaFxPath = Def.taskKey[File]("OpenJFX fetcher") +javaFxPath := { + val javaFxHome = + try { + val envHome = file(scala.sys.env("JAVAFX_HOME")) + if (envHome.toString.trim.isEmpty) throw new Exception("Empty JAVAFX_HOME") + println("Using OpenJFX from " + envHome) + envHome + } + catch { case _: Throwable => + println("Using local OpenJFX") + baseDirectory.value / "openjfx" + } + + if (!javaFxHome.exists()) java.nio.file.Files.createDirectory(javaFxHome.toPath) + + val jfx_os = osName.value match { + case "linux" => "linux" + case "mac" => "osx" + case "win" => "windows" + } + + val sdkURL = "http://download2.gluonhq.com/openjfx/" + jfx_sdk_version + "/openjfx-" + jfx_sdk_version + "_" + jfx_os + "-x64_bin-sdk.zip" + + try { + val testDir = javaFxHome / "all.ok" + if (!testDir.exists()) { + println("Fetching OpenJFX from "+sdkURL+"..") + IO.unzipURL(new URL(sdkURL), javaFxHome) + java.nio.file.Files.createDirectory(testDir.toPath) + println("Fetching OpenJFX done.") + } else { + println("Using OpenJFX from "+javaFxHome) + } + + javaFxHome + } + catch { + case t: Throwable => fail("Could not load OpenJFX! Reason:" + t.getMessage) + } +} + +val jfxModules = Seq("base","controls","fxml","graphics","media","swing","web") + + +if (!useJavaFX) Seq() else javafx_versions._1 match { + case 7 => + // TODO libraryDependencies + Seq(unmanagedJars in Compile += Attributed.blank(legacyJavaFX())) + case 8 => + (if (useScalaOrScalaFX) Seq(libraryDependencies += "org.scalafx" %% "scalafx" % jfx_scalafx_version) else Seq()) ++ + Seq(unmanagedJars in Compile += Attributed.blank(legacyJavaFX())) + case _ => + Seq( + javaOptions in run ++= Seq( + "--module-path", (javaFxPath.value / ("javafx-sdk-" + jfx_sdk_version) / "lib").toString, + "--add-modules=" + jfxModules.map("javafx."+_).mkString(",")) + ) ++ + (if (useScalaOrScalaFX) Seq(libraryDependencies += "org.scalafx" % "scalafx_2.13" % jfx_scalafx_version) else Seq()) ++ + jfxModules.map(module => libraryDependencies += "org.openjfx" % ("javafx-"+module) % jfx_sdk_version classifier osName.value) +} diff --git a/Project 3/pom.xml b/Project 3/pom.xml new file mode 100644 index 0000000..d5e6482 --- /dev/null +++ b/Project 3/pom.xml @@ -0,0 +1,452 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <!-- + + Welcome! + This pom.xml is *THE* file that defines your Maven style Java project. + + Eclipse, IDEA and other development environments with Maven support + or plugins can *import* this project by reading this file. + + It usually contains tons of barely readable configuration. Luckily + this basic pom.xml is somewhat readable. All necessary configuration + for customizing your project if located here on top before the + 'END OF SIMPLE CONFIGURATION' line below. + + Some basics: + + - Maven is a build system for Java/JVM + - the Maven projects define an artifact that has a three part id: + groupId - artifactId - version + + - For example, this project is called: + fi.utu.tech - oomkit - 1.15 + + - The group id is usually a web domain in reverse order. + + - You can use these id parts to search for projects at + https://search.maven.org/ + + - If your version is not final and you have plans to modify the + source code at some point, please use a version id that ends with + -SNAPSHOT, e.g 1.0-SNAPSHOT. Maven may cache the project jar + and refuse to overwrite old cached versions with new ones unless + you remember this convention. + + - This file supports + compiling the project with 'mvn compile' (see target/) + cleaning the class files with 'mvn clean' + executing the main routine with 'mvn exec:java' + executing the unit tests with 'mvn test' + packaging the application with 'mvn package' (see target/) + + - the Maven project structure is as follows: + https://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html + + --> + + <!-- ==== START OF SIMPLE CONFIGURATION ==== --> + + <!-- the three parts of the artifact name --> + <groupId>fi.utu.tech</groupId> + <artifactId>template-javafx</artifactId> + <version>1.0</version> + + <!-- additional information about the project --> + <name>JavaFX project template</name> + <url>https://gitlab.utu.fi/tech/education/gui/template-javafx</url> + + <packaging>jar</packaging> + + <!-- HINT: More configuration here! --> + <properties> + <!-- Configures this project to use 'fi.utu.tech.AppMain' as its main class --> + <project.mainclass>fi.utu.tech.gui.javafx.Main</project.mainclass> + + + <!-- Don't touch these unless you know what you're doing! + + For example, the source encoding should always be utf-8. + You're probably doing something stupid if you think it + should be a 8-bit code page in 2019. --> + <jdk.version>11</jdk.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <jqwik.version>1.2.0</jqwik.version> + <junit.version>5.5.2</junit.version> + <junitplatform.version>1.5.2</junitplatform.version> + <javafx.version>13.0.2</javafx.version> + </properties> + + <!-- ==== END OF SIMPLE CONFIGURATION ==== --> + + <repositories> + <repository> + <snapshots> + <enabled>false</enabled> + </snapshots> + <id>central</id> + <name>Central Repository</name> + <url>https://repo.maven.apache.org/maven2</url> + </repository> + <repository> + <id>jcenter</id> + <name>jcenter</name> + <url>https://jcenter.bintray.com/</url> + </repository> + <!-- UTU repository --> + <repository> + <id>ftdev</id> + <name>ftdev</name> + <url>https://ftdev.utu.fi/maven2</url> + </repository> + </repositories> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-commons</artifactId> + <version>${junitplatform.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>net.jqwik</groupId> + <artifactId>jqwik</artifactId> + <version>${jqwik.version}</version> + <scope>test</scope> + </dependency><!-- + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-migrationsupport</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-runner</artifactId> + <version>${junitplatform.version}</version> + <scope>test</scope> + </dependency> --> + + <!-- JavaFX (remove if not needed to speed up dep downloads)--> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-base</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-fxml</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-media</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-swing</artifactId> + <version>${javafx.version}</version> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-web</artifactId> + <version>${javafx.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Make a 'fat' jar, that is, jar that contains all its dependencies and runs as is. + See: https://stackoverflow.com/a/57691362 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>module-info.class</exclude> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>${project.mainclass}</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + + <!-- Run this app with exec:java --> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.6.0</version> + <executions> + <execution> + <goals> + <goal>java</goal> + </goals> + </execution> + </executions> + <configuration> + <mainClass>${project.mainclass}</mainClass> + <arguments> + <argument>arg1</argument> + <argument>arg2</argument> + </arguments> + </configuration> + </plugin> + + <!-- Make the packaged jar executable --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.1.2</version> + <configuration> + <!-- DO NOT include log4j.properties file in your Jar --> + <excludes> + <exclude>**/log4j.properties</exclude> + </excludes> + <archive> + <manifest> + <!-- Jar file entry point --> + <mainClass>${project.mainclass}</mainClass> + </manifest> + </archive> + </configuration> + </plugin> + + <!-- download source code in Eclipse, best practice --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-eclipse-plugin</artifactId> + <version>2.10</version> + <configuration> + <downloadSources>true</downloadSources> + <downloadJavadocs>false</downloadJavadocs> + </configuration> + </plugin> + + <!-- JDK source/target versions --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + <configuration> + <source>${jdk.version}</source> + <target>${jdk.version}</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.1.0</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>3.1.1</version> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + <configuration> + <tags> + <tag> + <name>toDo</name> + <placement>a</placement> + <head>To do:</head> + </tag> + <tag> + <name>classInvariant</name> + <placement>t</placement> + <head>Class invariant:</head> + </tag> + <tag> + <name>classInvariantProtected</name> + <placement>t</placement> + <head>Protected class invariant:</head> + </tag> + <tag> + <name>classInvariantPrivate</name> + <placement>t</placement> + <head>Private class invariant:</head> + </tag> + <tag> + <name>abstractionFunction</name> + <placement>t</placement> + <head>Abstraction function:</head> + </tag> + <tag> + <name>pre</name> + <placement>cm</placement> + <head>Precondition:</head> + </tag> + <tag> + <name>post</name> + <placement>cm</placement> + <head>Postcondition:</head> + </tag> + <tag> + <name>postProtected</name> + <placement>cm</placement> + <head>Protected postcondition:</head> + </tag> + <tag> + <name>postPrivate</name> + <placement>cm</placement> + <head>Private postcondition:</head> + </tag> + <tag> + <name>time</name> + <placement>cmf</placement> + <head>Time complexity:</head> + </tag> + <tag> + <name>space</name> + <placement>cmf</placement> + <head>Space complexity:</head> + </tag> + <tag> + <name>correspondence</name> + <placement>a</placement> + <head>Correspondence:</head> + </tag> + <tag> + <name>download</name> + <placement>a</placement> + <head>Download:</head> + </tag> + </tags> + <show>protected</show> + <failOnError>false</failOnError> + <sourceFileExcludes> + <sourceFileExclude>**/module-info.java</sourceFileExclude> + </sourceFileExcludes> + </configuration> + </plugin> + + <!-- JUnit & JQwik test integration --> + + <!-- junit-platform-maven-plugin: supports modular tests + maven-surefire-plugin: non-modular tests + + Modular testing works via command line mvn, but is + still broken in Eclipse due to this + https://bugs.eclipse.org/bugs/show_bug.cgi?id=520667 + --> + <plugin> + <groupId>de.sormuras.junit</groupId> + <artifactId>junit-platform-maven-plugin</artifactId> + <version>1.0.0-M5</version> + <extensions>true</extensions> + <configuration> + <executor>JAVA</executor> + </configuration> + </plugin> + + <!-- + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> + <plugin> + <artifactId>maven-failsafe-plugin</artifactId> + <version>3.0.0-M3</version> + </plugin> --> + + <!-- javafx:jlink: + https://github.com/openjfx/javafx-maven-plugin + Use 'mvn package' instead if you don't need jlink. + + <plugin> + <groupId>org.openjfx</groupId> + <artifactId>javafx-maven-plugin</artifactId> + <version>0.0.3</version> + <configuration> + <mainClass>${project.mainclass}</mainClass> + </configuration> + </plugin> --> + + </plugins> + <extensions> + <!-- Enables the use of SSH for deployments --> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh-external</artifactId> + <version>3.3.3</version> + </extension> + <extension> + <groupId>org.apache.maven.wagon</groupId> + <artifactId>wagon-ssh</artifactId> + <version>3.3.3</version> + </extension> + </extensions> + </build> + <distributionManagement> + <repository> + <id>ftdev</id> + <name>UTU tech ftdev repository</name> + <url>scp://localhost:2222/var/www/maven2</url> + </repository> + </distributionManagement> +</project> diff --git a/Project 3/project/build.properties b/Project 3/project/build.properties new file mode 100644 index 0000000..6adcdc7 --- /dev/null +++ b/Project 3/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.3 diff --git a/Project 3/project/plugins.sbt b/Project 3/project/plugins.sbt new file mode 100644 index 0000000..37ca261 --- /dev/null +++ b/Project 3/project/plugins.sbt @@ -0,0 +1,7 @@ +resolvers += Resolver.jcenterRepo + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9") +addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.8.3") +addSbtPlugin("com.github.xuwei-k" % "sbt-jshell" % "0.1.2") +//addSbtPlugin("org.xerial.sbt" % "sbt-sql-sqlite" % "0.8") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.4.0") diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java new file mode 100644 index 0000000..a4d0180 --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/Dialogs.java @@ -0,0 +1,13 @@ +package fi.utu.tech.gui.javafx; + +import javafx.scene.control.Alert; + +public class Dialogs { + public static void warning(String title, String header, String content) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(title); + alert.setHeaderText(header); + alert.setContentText(content); + alert.showAndWait(); + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/Main.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/Main.java new file mode 100644 index 0000000..ae8c3e3 --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/Main.java @@ -0,0 +1,31 @@ +package fi.utu.tech.gui.javafx; + +import fi.utu.tech.gui.javafx.gimmick.MainApp2; +import fi.utu.tech.gui.javafx.zipper.MainApp1; + +public class Main { + /** + * The main() method is ignored in correctly deployed JavaFX application. + * main() serves only as fallback in case the application can not be + * launched through deployment artifacts, e.g., in IDEs with limited FX + * support. NetBeans ignores main(). + * + * @param args the command line arguments + */ + public static void main(String[] args) { + System.out.println("Launching.."); + if (args.length == 1 && args[0].equals("--test")) return; + + int exercise = 2; + if (args.length == 1) exercise = Integer.parseInt(args[0]); + switch (exercise) { + case 1: + MainApp1.launch(MainApp1.class, args); + break; + default: + MainApp2.launch(MainApp2.class, args); + break; + } + } + +} diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/MainApp.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/MainApp.java new file mode 100644 index 0000000..519b92e --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/MainApp.java @@ -0,0 +1,40 @@ +package fi.utu.tech.gui.javafx; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.layout.BorderPane; +import javafx.scene.shape.Rectangle; +import javafx.stage.Stage; + +public class MainApp extends Application { + + // https://openjfx.io/javadoc/11/javafx.graphics/javafx/application/Application.html + + // The JavaFX runtime does the following, in order, whenever an application is launched: + + // 1. Starts the JavaFX runtime, if not already started (see Platform.startup(Runnable) for more information) + // 2. Constructs an instance of the specified Application class + // 3. Calls the Application's init() method + // 4. Calls the Application's start(javafx.stage.Stage) method + // 5. Waits for the application to finish, which happens when either of the following occur: + // a) the application calls Platform.exit() + // b) the last window has been closed and the implicitExit attribute on Platform is true + // 6. Calls the Application's stop() method + + @Override + public void init() { + } + + @Override + public void stop() { + } + + @Override + public void start(Stage stage) { + Scene scene = new Scene(new BorderPane(new Rectangle(300, 300))); + + stage.setTitle("JavaFX template"); + stage.setScene(scene); + stage.show(); + } +} diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java new file mode 100644 index 0000000..e5c2d1d --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/ResourceLoader.java @@ -0,0 +1,42 @@ +package fi.utu.tech.gui.javafx; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; + +public class ResourceLoader<N extends Parent, C> { + public final N root; + public final C controller; + + public ResourceLoader(String contentPath) { + N root_ = null; + C controller_ = null; + try { + // determines where to look for the resources (the root path) + Class resourceRootClass = getClass(); + + FXMLLoader loader = new FXMLLoader(resourceRootClass.getResource(contentPath)); + root_ = loader.load(); + controller_ = loader.getController(); + System.out.println("DEBUG: " + contentPath + " loaded."); + } catch (Exception e) { + Dialogs.warning( + "Internal error", + "Could not open FXML file: " + contentPath, + "Reason: " + e.getMessage() + ); + System.exit(1); + } + root = root_; + controller = controller_; + } + + // finds images both outside and inside jars + public static String image(String fileName) { + return ResourceLoader.class.getResource(fileName).toExternalForm(); + } + + // finds stylesheets both outside and inside jars + public static String stylesheet(String fileName) { + return ResourceLoader.class.getResource(fileName).toExternalForm(); + } +} diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/GimmickController.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/GimmickController.java new file mode 100644 index 0000000..871c432 --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/GimmickController.java @@ -0,0 +1,27 @@ +package fi.utu.tech.gui.javafx.gimmick; + +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.layout.TilePane; + +public class GimmickController { + @FXML + private TilePane tilePane; + + public void initialize() { + tilePane.getChildren().clear(); + + new Thread(() -> { + + for (int i = 0; i < 20; i++) { + final int j = i; + try { Thread.sleep(500); } + catch(Exception e) { + } + View c = j % 2 == 0 ? new View1(150, 150) : new View2(150, 150); + Platform.runLater(() -> tilePane.getChildren().add(c)); + new Thread(c).start(); + }}).start(); + + } +} diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/MainApp2.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/MainApp2.java new file mode 100644 index 0000000..353ad8d --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/MainApp2.java @@ -0,0 +1,20 @@ +package fi.utu.tech.gui.javafx.gimmick; + +import fi.utu.tech.gui.javafx.MainApp; +import fi.utu.tech.gui.javafx.ResourceLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class MainApp2 extends MainApp { + @Override + public void start(Stage stage) { + ResourceLoader<Parent, GimmickController> loader = new ResourceLoader<>("gimmick.fxml"); + + Scene scene = new Scene(loader.root); + + stage.setTitle("The Gimmick Show"); + stage.setScene(scene); + stage.show(); + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View.java new file mode 100644 index 0000000..666104e --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View.java @@ -0,0 +1,17 @@ +package fi.utu.tech.gui.javafx.gimmick; + +import javafx.scene.canvas.Canvas; + +abstract class View extends Canvas implements Runnable { + protected boolean active = true; + + public View(double width, double height) { + super(width, height); + + setOnMouseClicked(e -> { + active = false; + setVisible(false); + setManaged(false); + }); + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View1.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View1.java new file mode 100644 index 0000000..2fb6b9e --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View1.java @@ -0,0 +1,63 @@ +package fi.utu.tech.gui.javafx.gimmick; + +import javafx.scene.image.PixelFormat; +import javafx.scene.image.WritableImage; + +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; + + +class View1 extends View { + protected final int width, height; + private final WritableImage buffer; + private final int[] data; + + View1(int width, int height) { + super(width, height); + this.width = width; + this.height = height; + buffer = new WritableImage(width, height); + data = new int[width * height]; + } + + public void run() { + launchTimer(); + // uncomment to test if it fails (java.lang.InternalError). + // it should work if you managed to program this correctly! + //launchTimer(); + } + + void render() { + // uncomment to test if it fails + // it should work if you managed to program this correctly! + //if (!Platform.isFxApplicationThread()) throw new Error("Wrong thread!"); + + for (int pixelIdx = 0; pixelIdx < data.length; pixelIdx++) { + final int idx = pixelIdx; + new Thread(() -> { + data[idx] = new Random().nextInt(200) | ((new Random().nextInt(16) * 16) << 24); + }).run(); + } + + buffer.getPixelWriter().setPixels( + 0, 0, + width, height, + PixelFormat.getIntArgbPreInstance(), data, 0, width); + + getGraphicsContext2D().clearRect(0.0, 0.0, width, height); + getGraphicsContext2D().drawImage(buffer, 0.0, 0.0); + } + + void launchTimer() { + final Thread t = new Thread(() -> { + render(); + }); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + t.run(); + } + }, 0l, 1000l); + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View2.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View2.java new file mode 100644 index 0000000..be80667 --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/gimmick/View2.java @@ -0,0 +1,36 @@ +package fi.utu.tech.gui.javafx.gimmick; + +import javafx.scene.paint.Color; + +import java.util.Random; + +class View2 extends View { + protected final int width, height; + + View2(int width, int height) { + super(width, height); + this.width = width; + this.height = height; + } + + public void run() { + while (true) { + render(); + try { + Thread.sleep(200 + new Random().nextInt(10) * 100); + } catch (Exception e) { + } + } + } + + void render() { + // uncomment to test if it fails + // it should work if you managed to program this correctly! + //if (!Platform.isFxApplicationThread()) throw new Error("Wrong thread!"); + + getGraphicsContext2D().setFill(new Color(0.5,0,0,0.5)); + getGraphicsContext2D().fillRect(0.0, 0.0, width, height); + getGraphicsContext2D().setFill(new Color(0,0,1,0.9)); + getGraphicsContext2D().fillOval(new Random().nextDouble() * width, new Random().nextDouble() * height, 20, 20); + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/MainApp1.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/MainApp1.java new file mode 100644 index 0000000..9f4449e --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/MainApp1.java @@ -0,0 +1,27 @@ +package fi.utu.tech.gui.javafx.zipper; + +import fi.utu.tech.gui.javafx.MainApp; +import fi.utu.tech.gui.javafx.ResourceLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.stage.Stage; + +public class MainApp1 extends MainApp { + @Override + public void start(Stage stage) { + ResourceLoader<Parent, ZipperController> loader = new ResourceLoader<>("zipper.fxml"); + + + + loader.controller.setLabel("Welcome!"); + loader.controller.setStage(stage); + + Scene scene = new Scene(loader.root); + + stage.setTitle("Zip Master 2020"); + stage.setScene(scene); + stage.show(); + + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/Zipper.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/Zipper.java new file mode 100644 index 0000000..11ac8a0 --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/Zipper.java @@ -0,0 +1,134 @@ +package fi.utu.tech.gui.javafx.zipper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.concurrent.Task; +import javafx.scene.control.Label; + +public class Zipper extends Task<Void> { + + String sourceDir; + String destinationFile; + StringProperty currentFile = new SimpleStringProperty(); + StringProperty zipItButtonText = new SimpleStringProperty(); + FileOutputStream fos; + ZipOutputStream zipOut; + BooleanProperty currentlyZipping = new SimpleBooleanProperty(); + //Boolean currentlyZipping; + + public Zipper (String sourceDir, String destinationFile) { + this.sourceDir = sourceDir; + this.destinationFile = destinationFile; + try { + this.fos = new FileOutputStream(destinationFile); + this.zipOut = new ZipOutputStream(fos); + } + catch(Exception e) {} + + } + + //runlater + public Void call() { + + //if(currentlyZipping) { + // cancelZip(); + //} + //currentlyZipping.set(true); + //currentlyZipping = true; + //System.out.println("zipper "+ currentlyZipping.get()); + + try{ + + //Platform.runLater(() -> {zipItButtonText.set("Cancel"); + //}); + + zip(); + + //currentlyZipping.set(false); + //currentlyZipping = false; + + }catch(Exception e){} + return null; + } + + public void cancelZip() { + try { + + fos.close(); + + } catch(Exception e) {} + + Platform.runLater(() -> {zipItButtonText.set("Zip It!"); + }); + + Platform.runLater(() -> {currentFile.set("Zippaus peruutettu"); + }); + + } + + public void zip() throws IOException { + { + + + File fileToZip = new File(sourceDir); + + //currentFileLabel.setText(fileToZip.getName()); + + + + //System.out.println(fileToZip.getName()); + + zipFile(fileToZip, fileToZip.getName(), zipOut); + + } + } + + private void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException { + Platform.runLater(() -> {currentFile.set("Zipataan tiedostoa: " + fileToZip.getName()); + }); + if (fileToZip.isHidden()) { + return; + } + if (fileToZip.isDirectory()) { + if (fileName.endsWith("/")) { + zipOut.putNextEntry(new ZipEntry(fileName)); + zipOut.closeEntry(); + } else { + zipOut.putNextEntry(new ZipEntry(fileName + "/")); + zipOut.closeEntry(); + } + File[] children = fileToZip.listFiles(); + for (File childFile : children) { + zipFile(childFile, fileName + "/" + childFile.getName(), zipOut); + } + + Platform.runLater(() -> {currentFile.set("Valamis"); + }); + + return; + } + try (FileInputStream fis = new FileInputStream(fileToZip)) { + ZipEntry zipEntry = new ZipEntry(fileName); + zipOut.putNextEntry(zipEntry); + byte[] bytes = new byte[1024]; + int length; + while ((length = fis.read(bytes)) >= 0) { + zipOut.write(bytes, 0, length); + } + + Platform.runLater(() -> {currentFile.set("Valamis"); + }); + + } + } +} \ No newline at end of file diff --git a/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/ZipperController.java b/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/ZipperController.java new file mode 100644 index 0000000..8293271 --- /dev/null +++ b/Project 3/src/main/java/fi/utu/tech/gui/javafx/zipper/ZipperController.java @@ -0,0 +1,112 @@ +package fi.utu.tech.gui.javafx.zipper; + +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; +import java.io.IOException; + +public class ZipperController { + private Stage stage; + + public void setStage(Stage stage) { + this.stage = stage; + } + + @FXML + private TextField sourceField; + + @FXML + private TextField destField; + + @FXML + private Button zipItButton; + + public Label statusLabel; + + public Zipper zipper; + + public BooleanProperty zipping = new SimpleBooleanProperty(); + + public void setLabel(String text) { + if (!statusLabel.textProperty().isBound()) + statusLabel.setText(text); + } + + @FXML + void chooseDest(ActionEvent event) { + FileChooser chooser = new FileChooser(); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Zip files (.zip)", "*.zip")); + chooser.setTitle("Destination zip"); + File selected = chooser.showSaveDialog(stage); + if (selected == null) return; + destField.setText(selected.getAbsolutePath()); + if (!destField.getText().endsWith(".zip")) + destField.setText(destField.getText() + ".zip"); + setButtonState(null); + } + + @FXML + void chooseSource(ActionEvent event) { + DirectoryChooser chooser = new DirectoryChooser(); + chooser.setTitle("Source folder"); + File selected = chooser.showDialog(stage); + if (selected == null) return; + sourceField.setText(selected.getAbsolutePath()); + } + + @FXML + //yksisuuntanen bind statuslabelille, zipper status + void zipIt(ActionEvent event) { + try { + + + + zipper = new Zipper(sourceField.getText(), destField.getText()); + + //zipping.bind(zipper.currentlyZipping); + //zipper.currentlyZipping.bind(zipping); + + //zipping.set(true); + //System.out.println(zipping.get()); + + if(zipper.currentlyZipping.get()) { + zipper.cancelZip(); + + }else { + + new Thread(zipper).start(); + + //setLabel(zipper.currentFile); + + statusLabel.textProperty().bind(zipper.currentFile); + + zipItButton.textProperty().bind(zipper.zipItButtonText); + + + + } + + } catch (Exception e) { + setLabel(e.getMessage()); + } + } + + <T> void setButtonState(T t) { + zipItButton.setDisable(!destField.getText().endsWith(".zip")); + } + + public void initialize() { + setButtonState(null); + destField.setOnKeyTyped(this::setButtonState); + } +} \ No newline at end of file diff --git a/Project 3/src/main/resources/fi/utu/tech/gui/javafx/alien.png b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/alien.png new file mode 100644 index 0000000000000000000000000000000000000000..e94c775d601e2ae42159af0b4f86b7394ff3c47b GIT binary patch literal 46129 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 literal 0 HcmV?d00001 diff --git a/Project 3/src/main/resources/fi/utu/tech/gui/javafx/gimmick.fxml b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/gimmick.fxml new file mode 100644 index 0000000..f42f9b6 --- /dev/null +++ b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/gimmick.fxml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ScrollPane?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.StackPane?> +<?import javafx.scene.layout.TilePane?> +<?import javafx.scene.shape.Rectangle?> +<?import javafx.scene.text.Font?> + +<StackPane maxHeight="580.0" maxWidth="770.0" stylesheets="@styles.css" xmlns="http://javafx.com/javafx/11.0.0" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fi.utu.tech.gui.javafx.gimmick.GimmickController"> + <children> + <ImageView fitHeight="580.0" fitWidth="770.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@alien.png" /> + </image> + </ImageView> + <BorderPane> + <center> + <ScrollPane fitToHeight="true" fitToWidth="true" vbarPolicy="ALWAYS" BorderPane.alignment="CENTER"> + <content> + <TilePane fx:id="tilePane" alignment="CENTER" hgap="16.0" opacity="0.85" prefColumns="3" vgap="16.0"> + <padding> + <Insets bottom="8.0" left="8.0" right="8.0" top="8.0" /> + </padding> + <children> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + <Rectangle arcHeight="5.0" arcWidth="5.0" fill="DODGERBLUE" height="200.0" stroke="BLACK" strokeType="INSIDE" width="200.0" /> + </children> + </TilePane> + </content> + </ScrollPane> + </center> + <top> + <Label text="The Gimmick Show" BorderPane.alignment="CENTER"> + <font> + <Font size="32.0" /> + </font> + </Label> + </top> + </BorderPane> + </children> +</StackPane> diff --git a/Project 3/src/main/resources/fi/utu/tech/gui/javafx/hmm.png b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/hmm.png new file mode 100644 index 0000000000000000000000000000000000000000..cb06d8873e15f2f9f690347c2298dea696cac12e GIT binary patch literal 8752 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 KKbLh*2~7Zwj#I|~ literal 0 HcmV?d00001 diff --git a/Project 3/src/main/resources/fi/utu/tech/gui/javafx/package.png b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/package.png new file mode 100644 index 0000000000000000000000000000000000000000..643eff6d60113e5e16ab4426c1f3a47ac4878c01 GIT binary patch literal 8960 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 WQ&z6tsL8;<z~JfX=d#Wzp$PyhVUijE literal 0 HcmV?d00001 diff --git a/Project 3/src/main/resources/fi/utu/tech/gui/javafx/styles.css b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/styles.css new file mode 100644 index 0000000..d1e250b --- /dev/null +++ b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/styles.css @@ -0,0 +1,7 @@ +.scroll-pane { + -fx-background-color:transparent; +} + +.scroll-pane > .viewport { + -fx-background-color: transparent; +} diff --git a/Project 3/src/main/resources/fi/utu/tech/gui/javafx/zipper.fxml b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/zipper.fxml new file mode 100644 index 0000000..9a011d1 --- /dev/null +++ b/Project 3/src/main/resources/fi/utu/tech/gui/javafx/zipper.fxml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Font?> + +<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.0" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fi.utu.tech.gui.javafx.zipper.ZipperController"> + <left> + <ImageView fitHeight="368.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER"> + <image> + <Image url="@package.png" /> + </image> + <BorderPane.margin> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </BorderPane.margin> + </ImageView> + </left> + <center> + <BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <padding> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </padding> + <center> + <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="16.0" BorderPane.alignment="CENTER"> + <padding> + <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> + </padding> + <children> + <HBox alignment="CENTER_LEFT" prefHeight="30.0" prefWidth="200.0"> + <children> + <Label prefHeight="30.0" text="Source: " /> + <TextField fx:id="sourceField" prefHeight="30.0" HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets left="8.0" right="8.0" /> + </HBox.margin> + </TextField> + <Button maxHeight="30.0" maxWidth="30.0" mnemonicParsing="false" onAction="#chooseSource" prefHeight="30.0" prefWidth="30.0" text="..." /> + </children> + </HBox> + <HBox alignment="CENTER_LEFT" prefHeight="30.0" prefWidth="200.0"> + <children> + <Label prefHeight="30.0" text="Destination:" /> + <TextField fx:id="destField" prefHeight="30.0" HBox.hgrow="ALWAYS"> + <HBox.margin> + <Insets left="8.0" right="8.0" /> + </HBox.margin> + </TextField> + <Button maxHeight="30.0" maxWidth="30.0" mnemonicParsing="false" onAction="#chooseDest" prefHeight="30.0" prefWidth="30.0" text="..." /> + </children> + </HBox> + <Button fx:id="zipItButton" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#zipIt" text="Zip it!" /> + <Label fx:id="statusLabel" text="Loading..." /> + </children></VBox> + </center> + <top> + <Label text="Zip Master 2020" BorderPane.alignment="CENTER"> + <font> + <Font size="26.0" /> + </font></Label> + </top> + </BorderPane> + </center> +</BorderPane> -- GitLab