diff --git a/.gitignore b/.gitignore index 548f7a37..551570ae 100644 --- a/.gitignore +++ b/.gitignore @@ -7,13 +7,16 @@ out # Application stuff felix-cache plugins +.vertx # gradle files .gradle/ -gradle.properties -**/gradle.properties build **/build/ +# maven files +target +**/target/ + # Error files generated when JavaFX crashes hs_err_* diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 index 7a6bc1fc..8f2ef3a5 --- a/.travis.yml +++ b/.travis.yml @@ -13,22 +13,25 @@ sudo: true matrix: include: - os: linux - jdk: oraclejdk8 + dist: trusty + addons: + apt: + packages: + - fakeroot - os: osx - osx_image: xcode8.2 + osx_image: xcode9.2 before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export JAVA_HOME=$(/usr/libexec/java_home); fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export JAVA_HOME=$(/usr/libexec/java_home); else chmod +x travis.sh; ./travis.sh; export JAVA_HOME=~/jvm/jdk1.8.0_162; fi; + - chmod +x gradlew install: true script: - - chmod +x ./gradlew - - if [[ "$TRAVIS_BRANCH" == "master" ]]; then ./gradlew build bintrayUpload; else ./gradlew build; fi - - ./gradlew --stop + - if [[ "$TRAVIS_BRANCH" == "master" ]]; then ./gradlew clean build bintrayUpload -x :slideshowfx-icons:test; else ./gradlew clean build -x :slideshowfx-icons:test; fi cache: directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ \ No newline at end of file + - $HOME/.gradle + - $HOME/jvm diff --git a/CHANGELOG.textile b/CHANGELOG.textile index 7e148c31..8685b9e7 100755 --- a/CHANGELOG.textile +++ b/CHANGELOG.textile @@ -4,6 +4,61 @@ p. Changes to the SlideshowFX software are listed by version within this documen h2. Versions +h3(#2_0). "Version 2.0":#2_0 + +h4. New and noteworthy + +* Update the SlideshowFX-textile plugin ("#32":https://github.com/twasyl/SlideshowFX/issues/32) +* Update the code and snippet extension to support new programming language +* Alert content extension plugin uses new version of SweetAlert +* New Shape plugin allowing to insert shapes in a presentation +* Change the Windows default installation location +* Take the log configuration in consideration +* Creating a new presentation displays a template's library allowing to create presentations from previously used templates +* Display the current time in the information pane during a slideshow +* Adding support for speaker notes +* Ask for confirmation when deleting a presentation's variable +* Introduction of version for templates +* Allow to re-open recent presentations +* Uses ACE 1.3.1 in the slide content editor +* Using FontAwesome 5.0.8 +* Twitter integration has been improved + +h4. Bug fixes + +* Information screen during slideshow react to events emitted by the slideshow, meaning it will react correctly to display current and next slide +* Free information pane's resources correctly when exiting the slideshow mode +* Box hosting connector overwrite an existing file correctly when asked to +* Prevent NullPointerException to be thrown when auto saving is enabled and the presentation hasn't been already saved +* Allow Kotlin snippets to be executed on Windows + +h4. Included plugins within the packaging + +|_. Plugin |_. Version | +| SlideshowFX-alert-extension | 1.2 | +| SlideshowFX-asciidoctor | 1.1 | +| SlideshowFX-box-hosting-connector | 1.2 | +| SlideshowFX-code-extension | 1.2 | +| SlideshowFX-drive-hosting-connector | 1.3 | +| SlideshowFX-dropbox-hosting-connector | 1.2 | +| SlideshowFX-go-executor | 1.0 | +| SlideshowFX-golo-executor | 1.0 | +| SlideshowFX-groovy-executor | 1.0 | +| SlideshowFX-html | 1.0 | +| SlideshowFX-image-extension | 1.3 | +| SlideshowFX-java-executor | 1.0 | +| SlideshowFX-javascript-executor | 1.0 | +| SlideshowFX-kotlin-executor | 1.1 | +| SlideshowFX-link-extension | 1.2 | +| SlideshowFX-markdown | 1.0 | +| SlideshowFX-quiz-extension | 1.1 | +| SlideshowFX-quote-extension | 1.1 | +| SlideshowFX-scala-executor | 1.0 | +| SlideshowFX-sequence-diagram-extension | 1.1 | +| SlideshowFX-shape-extension | 1.0 | +| SlideshowFX-snippet-extension | 1.2 | +| SlideshowFX-textile | 1.2 | + h3(#1_4). "Version 1.4":#1_4 h4. New and noteworthy diff --git a/README.textile b/README.textile index 0864f675..bbfaf1d6 100755 --- a/README.textile +++ b/README.textile @@ -24,11 +24,11 @@ h1. How to get started p. As SlideshowFX is a still young project, you will need to build it manually in order to test it. In order to do so, you will need: -* A JDK 8 update 121 ; -* The @JAVA_HOME@ system variable set to point to you JDK 8 update 121 installation. +* A JDK 8 update 152 ; +* The @JAVA_HOME@ system variable set to point to you JDK 8 update 152 installation. p. Then in order to build the application: * Open a command line in the repository folder ; -* Execute the command @gradlew build -x test@ and wait for it to finish successfully ; -* Go in @REPOSITORY_HOME/build/distributions/@ in order to archive containing the SlideshowFX installer +* Execute the command @mvn clean package@ and wait for it to finish successfully ; +* Go in @REPOSITORY_HOME/SlideshowFX-setup/target@ in order to find the *SlideshowFXSetup.zip* archive containing the SlideshowFX installer diff --git a/SlideshowFX-alert-extension/build.gradle b/SlideshowFX-alert-extension/build.gradle index 036d49f1..8396bb89 100755 --- a/SlideshowFX-alert-extension/build.gradle +++ b/SlideshowFX-alert-extension/build.gradle @@ -1,42 +1,26 @@ description = 'Extension allowing to insert alerts inside a SlideshowFX presentation' -version = '1.1' +version = '1.2' -dependencies { - compile project(':SlideshowFX-content-extension') - compile configurations.felix - - testCompile configurations.junit -} +apply plugin: 'java-library' -jar { - manifest { - attributes('Manifest-Version': '1.0', - 'Bundle-ManifestVersion': '2', - 'Bundle-Name': 'SlideshowFX alert extension', - 'Bundle-SymbolicName': 'com.twasyl.slideshowfx.content.extension.alert', - 'Bundle-Description': 'Support for inserting alert in slides', - 'Bundle-Version': "$project.version", - 'Bundle-Activator': 'com.twasyl.slideshowfx.content.extension.alert.activator.AlertContentExtensionActivator', - 'Bundle-Vendor': 'Thierry Wasylczenko', - 'Export-Package': 'com.twasyl.slideshowfx.content.extension.alert.controllers,com.twasyl.slideshowfx.content.extension.alert,com.twasyl.slideshowfx.content.extension.alert.activator', - 'Import-Package': 'org.osgi.framework', - 'Setup-Wizard-Label': 'Alert', - 'Setup-Wizard-Icon-Name': 'EXCLAMATION_TRIANGLE') - } -} - -bintray { - - configurations = ['archives'] +dependencies { + api project(':slideshowfx-content-extension') + implementation project(':slideshowfx-icons') + api project(':slideshowfx-markup') + api project(':slideshowfx-plugin') + api project(':slideshowfx-ui-controls') - pkg { - version { - name = project.version - desc = project.description - released = new Date() - vcsTag = "v${project.version}" - } - } + testCompile "org.mockito:mockito-core:${rootProject.ext.dependencies.mockito.version}" } -tasks.bintrayUpload.enabled = alertContentExtensionBintrayUploadEnabled \ No newline at end of file +ext.isPlugin = true +ext.isContentExtension = true +ext.bundle = [ + name : 'SlideshowFX alert extension', + symbolicName : 'com.twasyl.slideshowfx.content.extension.alert', + description : 'Support for inserting alert in slides', + activator : 'com.twasyl.slideshowfx.content.extension.alert.activator.AlertContentExtensionActivator', + exportPackage : 'com.twasyl.slideshowfx.content.extension.alert.controllers,com.twasyl.slideshowfx.content.extension.alert,com.twasyl.slideshowfx.content.extension.alert.activator', + setupWizardLabel : 'Alert', + setupWizardIconName: 'EXCLAMATION_TRIANGLE' +] \ No newline at end of file diff --git a/SlideshowFX-alert-extension/src/main/java/com/twasyl/slideshowfx/content/extension/alert/controllers/AlertContentExtensionController.java b/SlideshowFX-alert-extension/src/main/java/com/twasyl/slideshowfx/content/extension/alert/controllers/AlertContentExtensionController.java index 186eefdc..cdb9cd15 100755 --- a/SlideshowFX-alert-extension/src/main/java/com/twasyl/slideshowfx/content/extension/alert/controllers/AlertContentExtensionController.java +++ b/SlideshowFX-alert-extension/src/main/java/com/twasyl/slideshowfx/content/extension/alert/controllers/AlertContentExtensionController.java @@ -1,11 +1,13 @@ package com.twasyl.slideshowfx.content.extension.alert.controllers; +import com.twasyl.slideshowfx.content.extension.AbstractContentExtensionController; import com.twasyl.slideshowfx.ui.controls.ExtendedTextField; +import com.twasyl.slideshowfx.ui.controls.validators.Validators; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.fxml.FXML; -import javafx.fxml.Initializable; import javafx.scene.control.CheckBox; import javafx.scene.control.RadioButton; -import javafx.scene.control.TextField; import javafx.scene.control.ToggleGroup; import java.net.URL; @@ -13,42 +15,59 @@ /** * This class is the controller used by the {@code AlertContentExtension.fxml} file. + * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.0 */ -public class AlertContentExtensionController implements Initializable { +public class AlertContentExtensionController extends AbstractContentExtensionController { - @FXML private ExtendedTextField title; - @FXML private ExtendedTextField text; - @FXML private ToggleGroup typeGroup; - @FXML private RadioButton infoRB; - @FXML private RadioButton successRB; - @FXML private RadioButton warningRB; - @FXML private RadioButton errorRB; - @FXML private CheckBox allowClickOutside; - @FXML private CheckBox showCancelButton; - @FXML private ExtendedTextField buttonText; + @FXML + private ExtendedTextField title; + @FXML + private ExtendedTextField text; + @FXML + private ToggleGroup typeGroup; + @FXML + private RadioButton infoRB; + @FXML + private RadioButton successRB; + @FXML + private RadioButton warningRB; + @FXML + private RadioButton errorRB; + @FXML + private CheckBox allowClickOutside; + @FXML + private CheckBox showCancelButton; + @FXML + private ExtendedTextField buttonText; /** * Return the title entered in the UI for this alert. + * * @return The title of the alert. */ - public String getTitle() { return this.title.getText(); } + public String getTitle() { + return this.title.getText(); + } /** * Return the additional text entered in the UI for this alert. + * * @return The text for this alert. */ - public String getText() { return this.text.getText(); } + public String getText() { + return this.text.getText(); + } /** * Return the type of the alert. Currently four values can be returned: * * * @return Return the type of this alert. @@ -56,21 +75,36 @@ public class AlertContentExtensionController implements Initializable { public String getType() { String type = null; - if(this.typeGroup.getSelectedToggle() == this.infoRB) type = "info"; - else if(this.typeGroup.getSelectedToggle() == this.successRB) type = "success"; - else if(this.typeGroup.getSelectedToggle() == this.warningRB) type = "warning"; - else if(this.typeGroup.getSelectedToggle() == this.errorRB) type = "error"; + if (this.typeGroup.getSelectedToggle() == this.infoRB) type = "info"; + else if (this.typeGroup.getSelectedToggle() == this.successRB) type = "success"; + else if (this.typeGroup.getSelectedToggle() == this.warningRB) type = "warning"; + else if (this.typeGroup.getSelectedToggle() == this.errorRB) type = "error"; return type; } - public boolean isClickOutsideAllowed() { return this.allowClickOutside.isSelected(); } + public boolean isClickOutsideAllowed() { + return this.allowClickOutside.isSelected(); + } - public boolean isCancelButtonVisible() { return this.showCancelButton.isSelected(); } + public boolean isCancelButtonVisible() { + return this.showCancelButton.isSelected(); + } - public String getButtonText() { return this.buttonText.getText(); } + public String getButtonText() { + return this.buttonText.getText(); + } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + final ReadOnlyBooleanWrapper property = new ReadOnlyBooleanWrapper(); + property.bind(this.title.validProperty()); + + return property; + } @Override public void initialize(URL location, ResourceBundle resources) { + this.title.setValidator(Validators.isNotEmpty()); } } diff --git a/SlideshowFX-alert-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/alert/resources/sweetalert.zip b/SlideshowFX-alert-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/alert/resources/sweetalert.zip index d7ff9925..0ff99ebe 100644 Binary files a/SlideshowFX-alert-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/alert/resources/sweetalert.zip and b/SlideshowFX-alert-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/alert/resources/sweetalert.zip differ diff --git a/SlideshowFX-alert-extension/src/test/java/com/twasyl/slideshowfx/content/extension/alert/AlertContentExtensionTest.java b/SlideshowFX-alert-extension/src/test/java/com/twasyl/slideshowfx/content/extension/alert/AlertContentExtensionTest.java old mode 100755 new mode 100644 index 9e9b7e42..7fd58687 --- a/SlideshowFX-alert-extension/src/test/java/com/twasyl/slideshowfx/content/extension/alert/AlertContentExtensionTest.java +++ b/SlideshowFX-alert-extension/src/test/java/com/twasyl/slideshowfx/content/extension/alert/AlertContentExtensionTest.java @@ -1,27 +1,209 @@ package com.twasyl.slideshowfx.content.extension.alert; -import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.stage.Stage; +import com.twasyl.slideshowfx.content.extension.alert.controllers.AlertContentExtensionController; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.text.MessageFormat; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; /** * @author Thierry Wasylczenko - * @since SlideshowFX 1.3 + * @version 1.0 + * @since SlideshowFX 2.0 */ -public class AlertContentExtensionTest extends Application { +public class AlertContentExtensionTest { + public static String EXPECTED_SCRIPT_WITH_TEXT_FORMAT = "\n" + + ""; + + public static String EXPECTED_SCRIPT_WITHOUT_TEXT_FORMAT = "\n" + + ""; - @Override - public void start(Stage primaryStage) throws Exception { - final Parent root = FXMLLoader.load(AlertContentExtensionTest.class.getResource("/com/twasyl/slideshowfx/content/extension/alert/fxml/AlertContentExtension.fxml")); + public static AlertContentExtension extension; + public static String GENERATED_ID = "swal-btn-" + System.currentTimeMillis(); + + @BeforeAll + public static void setUp() { + extension = spy(new AlertContentExtension()); + when(extension.generateID()).thenReturn(GENERATED_ID); + } - final Scene scene = new Scene(root); - primaryStage.setScene(scene); - primaryStage.show(); + @BeforeEach + public void before() { + final AlertContentExtensionController controller = spy(new AlertContentExtensionController()); + extension.controller = controller; } - public static void main(String[] args) { - launch(args); + @Test + public void buildDefaultAlertWithText() { + final String title = "Alert's title"; + final boolean cancelButtonVisible = true; + final boolean confirmButtonVisible = true; + final String buttonsText = "My button"; + final boolean closeOnEsc = true; + final boolean clickOutsideAllowed = true; + final String alertsText = "Text of the alert"; + final String alertsType = "info"; + + doReturn(cancelButtonVisible).when(extension.controller).isCancelButtonVisible(); + doReturn(clickOutsideAllowed).when(extension.controller).isClickOutsideAllowed(); + doReturn(buttonsText).when(extension.controller).getButtonText(); + doReturn(alertsText).when(extension.controller).getText(); + doReturn(title).when(extension.controller).getTitle(); + doReturn(alertsType).when(extension.controller).getType(); + + final String expected = MessageFormat.format(EXPECTED_SCRIPT_WITH_TEXT_FORMAT, GENERATED_ID, title, alertsText, alertsType, confirmButtonVisible, cancelButtonVisible, clickOutsideAllowed, closeOnEsc); + final String content = extension.buildDefaultContentString(); + + assertEquals(expected, content); + } + + @Test + public void buildDefaultAlertWithoutText() { + final String title = "Alert's title"; + final boolean cancelButtonVisible = true; + final boolean confirmButtonVisible = true; + final String buttonsText = "My button"; + final boolean closeOnEsc = true; + final boolean clickOutsideAllowed = true; + final String alertsType = "info"; + + doReturn(cancelButtonVisible).when(extension.controller).isCancelButtonVisible(); + doReturn(clickOutsideAllowed).when(extension.controller).isClickOutsideAllowed(); + doReturn(buttonsText).when(extension.controller).getButtonText(); + doReturn("").when(extension.controller).getText(); + doReturn(title).when(extension.controller).getTitle(); + doReturn(alertsType).when(extension.controller).getType(); + + final String expected = MessageFormat.format(EXPECTED_SCRIPT_WITHOUT_TEXT_FORMAT, GENERATED_ID, title, alertsType, confirmButtonVisible, cancelButtonVisible, clickOutsideAllowed, closeOnEsc); + final String content = extension.buildDefaultContentString(); + + assertEquals(expected, content); + } + + @Test + public void buildDefaultInfoAlert() { + final String title = "Alert's title"; + final boolean cancelButtonVisible = true; + final boolean confirmButtonVisible = true; + final String buttonsText = "My button"; + final boolean closeOnEsc = true; + final boolean clickOutsideAllowed = true; + final String alertsText = "Text of the alert"; + final String alertsType = "info"; + + doReturn(cancelButtonVisible).when(extension.controller).isCancelButtonVisible(); + doReturn(clickOutsideAllowed).when(extension.controller).isClickOutsideAllowed(); + doReturn(buttonsText).when(extension.controller).getButtonText(); + doReturn(alertsText).when(extension.controller).getText(); + doReturn(title).when(extension.controller).getTitle(); + doReturn(alertsType).when(extension.controller).getType(); + + final String expected = MessageFormat.format(EXPECTED_SCRIPT_WITH_TEXT_FORMAT, GENERATED_ID, title, alertsText, alertsType, confirmButtonVisible, cancelButtonVisible, clickOutsideAllowed, closeOnEsc); + final String content = extension.buildDefaultContentString(); + + assertEquals(expected, content); + } + + @Test + public void buildDefaultSuccessAlert() { + final String title = "Alert's title"; + final boolean cancelButtonVisible = true; + final boolean confirmButtonVisible = true; + final String buttonsText = "My button"; + final boolean closeOnEsc = true; + final boolean clickOutsideAllowed = true; + final String alertsText = "Text of the alert"; + final String alertsType = "success"; + + doReturn(cancelButtonVisible).when(extension.controller).isCancelButtonVisible(); + doReturn(clickOutsideAllowed).when(extension.controller).isClickOutsideAllowed(); + doReturn(buttonsText).when(extension.controller).getButtonText(); + doReturn(alertsText).when(extension.controller).getText(); + doReturn(title).when(extension.controller).getTitle(); + doReturn(alertsType).when(extension.controller).getType(); + + final String expected = MessageFormat.format(EXPECTED_SCRIPT_WITH_TEXT_FORMAT, GENERATED_ID, title, alertsText, alertsType, confirmButtonVisible, cancelButtonVisible, clickOutsideAllowed, closeOnEsc); + final String content = extension.buildDefaultContentString(); + + assertEquals(expected, content); + } + + @Test + public void buildDefaultWarningAlert() { + final String title = "Alert's title"; + final boolean cancelButtonVisible = true; + final boolean confirmButtonVisible = true; + final String buttonsText = "My button"; + final boolean closeOnEsc = true; + final boolean clickOutsideAllowed = true; + final String alertsText = "Text of the alert"; + final String alertsType = "warning"; + + doReturn(cancelButtonVisible).when(extension.controller).isCancelButtonVisible(); + doReturn(clickOutsideAllowed).when(extension.controller).isClickOutsideAllowed(); + doReturn(buttonsText).when(extension.controller).getButtonText(); + doReturn(alertsText).when(extension.controller).getText(); + doReturn(title).when(extension.controller).getTitle(); + doReturn(alertsType).when(extension.controller).getType(); + + final String expected = MessageFormat.format(EXPECTED_SCRIPT_WITH_TEXT_FORMAT, GENERATED_ID, title, alertsText, alertsType, confirmButtonVisible, cancelButtonVisible, clickOutsideAllowed, closeOnEsc); + final String content = extension.buildDefaultContentString(); + + assertEquals(expected, content); + } + + @Test + public void buildDefaultErrorAlert() { + final String title = "Alert's title"; + final boolean cancelButtonVisible = true; + final boolean confirmButtonVisible = true; + final String buttonsText = "My button"; + final boolean closeOnEsc = true; + final boolean clickOutsideAllowed = true; + final String alertsText = "Text of the alert"; + final String alertsType = "error"; + + doReturn(cancelButtonVisible).when(extension.controller).isCancelButtonVisible(); + doReturn(clickOutsideAllowed).when(extension.controller).isClickOutsideAllowed(); + doReturn(buttonsText).when(extension.controller).getButtonText(); + doReturn(alertsText).when(extension.controller).getText(); + doReturn(title).when(extension.controller).getTitle(); + doReturn(alertsType).when(extension.controller).getType(); + + final String expected = MessageFormat.format(EXPECTED_SCRIPT_WITH_TEXT_FORMAT, GENERATED_ID, title, alertsText, alertsType, confirmButtonVisible, cancelButtonVisible, clickOutsideAllowed, closeOnEsc); + final String content = extension.buildDefaultContentString(); + + assertEquals(expected, content); } } diff --git a/SlideshowFX-app/build.gradle b/SlideshowFX-app/build.gradle index 925a0f48..42c5cddb 100755 --- a/SlideshowFX-app/build.gradle +++ b/SlideshowFX-app/build.gradle @@ -1,81 +1,95 @@ -import com.sun.javafx.PlatformUtil +description = 'The SlideshowFX application' +version = '2.0' -version = '1.4' +apply plugin: 'java' dependencies { - compile project(':SlideshowFX-engines') - compile project(':SlideshowFX-global-configuration') - compile project(':SlideshowFX-plugin') - compile project(':SlideshowFX-osgi') - compile project(":SlideshowFX-server") - compile project(':SlideshowFX-utils') - - compile project(':SlideshowFX-content-extension') - compile project(':SlideshowFX-hosting-connector') - compile project(':SlideshowFX-markup') - compile project(':SlideshowFX-snippet-executor') - - compile configurations.asciidoctorj - compile configurations.felix - compile configurations.fontawesomefx - compile configurations.freemarker - compile configurations.jsoup - compile configurations.twitter4j - compile configurations.vertx - compile configurations.zxing - - testCompile configurations.junit - testCompile configurations.mockito + compile project(':slideshowfx-content-extension') + compile project(':slideshowfx-documentation') + compile project(':slideshowfx-engines') + compile project(':slideshowfx-global-configuration') + compile project(':slideshowfx-hosting-connector') + compile project(':slideshowfx-icons') + compile project(':slideshowfx-logs') + compile project(':slideshowfx-markup') + compile project(':slideshowfx-osgi') + compile project(':slideshowfx-plugin') + compile project(':slideshowfx-server') + compile project(':slideshowfx-snippet-executor') + compile project(':slideshowfx-ui-controls') + compile project(':slideshowfx-utils') + + compile "io.reactivex.rxjava2:rxjava:${rootProject.ext.dependencies.rxJava.version}" + compile "com.google.zxing:javase:${rootProject.ext.dependencies.zxing.version}" + + testCompile "org.mockito:mockito-core:${rootProject.ext.dependencies.mockito.version}" +} + +ext.packaging = [ + extension: rootProject.ext.os.MAC == rootProject.getPlatform() ? ".app" : "" +] + +processResources { + doLast { + project(':slideshowfx-documentation').unzipDocumentationsIn("${buildDir}/resources/main/com/twasyl/slideshowfx/documentation", true) + } } jar { manifest { - attributes("Implementation-Title": "SlideshowFX", - "Implementation-Version": version, - "Implementation-Vendor": "Thierry Wasylczenko", - "JavaFX-Preloader-Class": "com.twasyl.slideshowfx.app.SlideshowFXPreloader", - "Main-Class": "com.twasyl.slideshowfx.app.SlideshowFX", - "JavaFX-Version": "8.0+", - "Class-Path": configurations.compile.collect { it.getName() }.join(' ')) + attributes += [ + 'Implementation-Title' : 'SlideshowFX', + 'Implementation-Version' : "${version}", + 'Class-Path' : configurations.compile.collect { it.getName() }.join(' '), + 'Main-Class' : 'com.twasyl.slideshowfx.app.SlideshowFX', + 'JavaFX-Preloader-Class' : 'com.twasyl.slideshowfx.app.SlideshowFXPreloader', + 'JavaFX-Application-Class': 'com.twasyl.slideshowfx.app.SlideshowFX', + 'JavaFX-Version' : '8.0+' + ] } } -test { - workingDir = rootDir - jvmArgs "-DtestResultsDir=${testResultsDir.absolutePath}" +task prepareBundle(type: Copy) { + from configurations.compile + from jar + + into "${buildDir}/tmp/bundles" } -task javafx(dependsOn: [ 'jar' ]) << { - def javafxFolder = new File(buildDir, 'javafx') - def resourcesFolder = new File(buildDir, "resources") +task createBundle() { + outputs.files("${buildDir}/bundle/bundles/SlideshowFX${project.ext.packaging.extension}") + doLast { + def deployOutDir = new File("${buildDir}/bundle") + def deployResourcesDir = new File("${buildDir}/tmp/bundles") - // Create the JavaFX bundle - ant.importBuild "${rootDir.absolutePath}/src/main/resources/javafx/${project.name}.xml" - ant.classpath = "${jdk}/lib/ant-javafx.jar" + ant.importBuild "${projectDir}/src/assembly/javafx/SlideshowFX-app.xml" + ant.classpath = "${System.env['JAVA_HOME']}/lib/ant-javafx.jar" + ant['build.deploy.out.dir'] = deployOutDir.absolutePath + ant['build.deploy.out.file'] = "SlideshowFX" + ant['build.deploy.resources.dir'] = deployResourcesDir.absolutePath - ant.deployOutDir = javafxFolder.absolutePath - ant.deployResourcesDir = resourcesFolder.absolutePath + if (!new File(ant['build.deploy.out.dir']).exists()) { + new File(ant['build.deploy.out.dir']).mkdirs() + } - // Copy libs - copy { - from project.configurations.compile - from project.jar.archivePath + def platform = rootProject.getPlatform(); - into(resourcesFolder) - } + if (platform == rootProject.ext.os.MAC) { + deployOSX.execute() + } else if (platform == rootProject.ext.os.WINDOWS) { + deployWindows.execute() + } else { + deployUnknown.execute() + } - if(PlatformUtil.isMac()) { - println "Create ${project.name} package for the OSX platform" - deployOSX.execute() - } else if(PlatformUtil.isWindows()) { - println "Create ${project.name} package for the Windows platform" - deployWindows.execute() - } else { - println "Create ${project.name} package an unknown platform" - deployUnknown.execute() + deployResourcesDir.deleteDir() + new File(deployOutDir, "package").deleteDir() + delete fileTree(dir: deployOutDir, exclude: 'bundles') + delete fileTree(dir: "${buildDir}/bundle/bundles", exclude: "SlideshowFX${project.ext.packaging.extension}") } - - resourcesFolder.deleteDir() } -tasks.bintrayUpload.enabled = false \ No newline at end of file +processResources.dependsOn project(':slideshowfx-documentation').distZip +prepareBundle.dependsOn jar +createBundle.dependsOn prepareBundle +build.dependsOn createBundle \ No newline at end of file diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/app/SlideshowFX.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/app/SlideshowFX.java index 5dd5cff8..6aa257a4 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/app/SlideshowFX.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/app/SlideshowFX.java @@ -7,6 +7,7 @@ import com.twasyl.slideshowfx.hosting.connector.IHostingConnector; import com.twasyl.slideshowfx.osgi.OSGiManager; import com.twasyl.slideshowfx.server.SlideshowFXServer; +import com.twasyl.slideshowfx.utils.DialogHelper; import com.twasyl.slideshowfx.utils.io.DeleteFileVisitor; import com.twasyl.slideshowfx.utils.time.DateTimeUtils; import javafx.application.Application; @@ -16,6 +17,7 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; +import javafx.scene.text.Font; import javafx.stage.Stage; import java.io.File; @@ -42,32 +44,38 @@ public class SlideshowFX extends Application { private static final ReadOnlyObjectProperty stage = new SimpleObjectProperty<>(); private static final ReadOnlyObjectProperty presentationBuilderScene = new SimpleObjectProperty<>(); - private final ReadOnlyObjectProperty mainController = new SimpleObjectProperty<>(); + private static final ReadOnlyObjectProperty mainController = new SimpleObjectProperty<>(); private Set filesToOpen; @Override public void init() throws Exception { + Font.loadFont(SlideshowFX.class.getResource("/com/twasyl/slideshowfx/fonts/Inconsolata-Regular.ttf").toExternalForm(), 12); + // Initialize the configuration GlobalConfiguration.createApplicationDirectory(); + GlobalConfiguration.createTemplateLibraryDirectory(); - if(GlobalConfiguration.createConfigurationFile()) { - GlobalConfiguration.fillConfigurationWithDefaultValue(); + if (GlobalConfiguration.createConfigurationFile()) { + GlobalConfiguration.fillConfigurationWithDefaultValue(); } else { GlobalConfiguration.fillConfigurationWithDefaultValue(); } + if (GlobalConfiguration.createLoggingConfigurationFile()) { + GlobalConfiguration.fillLoggingConfigurationFileWithDefaultValue(); + } + // Start the MarkupManager LOGGER.info("Starting Felix"); OSGiManager.getInstance().startAndDeploy(); - // Retrieve the files to open at startup final Map params = getParameters().getNamed(); - if(params != null && !params.isEmpty()) { + if (params != null && !params.isEmpty()) { this.filesToOpen = new HashSet<>(); // Only files that exist and can be read and opened are added to the list of files to open params.forEach((paramName, paramValue) -> { - if(paramName != null && (paramName.startsWith(PRESENTATION_ARGUMENT_PREFIX) || + if (paramName != null && (paramName.startsWith(PRESENTATION_ARGUMENT_PREFIX) || paramName.startsWith(TEMPLATE_ARGUMENT_PREFIX))) { final File file = new File(paramValue); @@ -81,11 +89,11 @@ public void init() throws Exception { // Try to load parameters that are passed dynamically with just a value final List unnamedParams = getParameters().getUnnamed(); - if(unnamedParams != null && !unnamedParams.isEmpty()) { + if (unnamedParams != null && !unnamedParams.isEmpty()) { unnamedParams.forEach(param -> { final File file = new File(param); - if((file.getName().endsWith(TemplateEngine.DEFAULT_ARCHIVE_EXTENSION) || + if ((file.getName().endsWith(TemplateEngine.DEFAULT_ARCHIVE_EXTENSION) || file.getName().endsWith(PresentationEngine.DEFAULT_ARCHIVE_EXTENSION)) && file.exists() && file.canRead() && file.canWrite() && !this.filesToOpen.contains(file)) { this.filesToOpen.add(file); @@ -98,33 +106,38 @@ public void init() throws Exception { public void start(Stage stage) throws Exception { ((SimpleObjectProperty) SlideshowFX.stage).set(stage); - final FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/twasyl/slideshowfx/fxml/SlideshowFX.fxml")); - final Parent root = loader.load(); - ((SimpleObjectProperty) this.mainController).set(loader.getController()); - - final Scene scene = new Scene(root); - ((SimpleObjectProperty) presentationBuilderScene).set(scene); - - stage.setTitle("SlideshowFX"); - stage.setScene(scene); - stage.setMaximized(true); - stage.getIcons().addAll( - new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/16.png")), - new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/32.png")), - new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/64.png")), - new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/128.png")), - new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/256.png")), - new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/512.png"))); - stage.show(); - - if(this.filesToOpen != null && !this.filesToOpen.isEmpty()) { - this.filesToOpen.forEach(file -> { - try { - this.mainController.get().openTemplateOrPresentation(file); - } catch (IllegalAccessException | FileNotFoundException e) { - LOGGER.log(Level.SEVERE, "Can not open file at startup", e); - } - }); + try { + final FXMLLoader loader = new FXMLLoader(); + final Parent root = loader.load(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/fxml/SlideshowFX.fxml")); + ((SimpleObjectProperty) mainController).set(loader.getController()); + + final Scene scene = new Scene(root); + ((SimpleObjectProperty) presentationBuilderScene).set(scene); + + stage.setTitle("SlideshowFX"); + stage.setScene(scene); + stage.setMaximized(true); + stage.getIcons().addAll( + new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/16.png")), + new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/32.png")), + new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/64.png")), + new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/128.png")), + new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/256.png")), + new Image(SlideshowFX.class.getResourceAsStream("/com/twasyl/slideshowfx/images/appicons/512.png"))); + stage.show(); + + if (this.filesToOpen != null && !this.filesToOpen.isEmpty()) { + this.filesToOpen.forEach(file -> { + try { + mainController.get().openTemplateOrPresentation(file); + } catch (IllegalAccessException | FileNotFoundException e) { + LOGGER.log(Level.SEVERE, "Can not open file at startup", e); + } + }); + } + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, "Can not start the application", ex); + DialogHelper.showError("Error", "Can not start the application"); } } @@ -132,10 +145,13 @@ public void start(Stage stage) throws Exception { public void stop() throws Exception { super.stop(); - this.mainController.get().closeAllPresentations(true); + if (mainController.get() != null) { + mainController.get().closeAllPresentations(true); + + deleteTemporaryFiles(); + stopInternalServer(); + } - deleteTemporaryFiles(); - stopInternalServer(); stopOSGIManager(); } @@ -143,7 +159,7 @@ public void stop() throws Exception { * Deletes temporary files older than the configuration parameter {@link GlobalConfiguration#TEMPORARY_FILES_MAX_AGE_PARAMETER}. */ private void deleteTemporaryFiles() { - if(GlobalConfiguration.canDeleteTemporaryFiles()) { + if (GlobalConfiguration.canDeleteTemporaryFiles()) { LOGGER.info("Cleaning temporary files"); final File tempDirectory = new File(System.getProperty("java.io.tmpdir")); @@ -166,7 +182,7 @@ private void deleteTemporaryFiles() { * Stop the internal server if it is running. */ private void stopInternalServer() { - if(SlideshowFXServer.getSingleton() != null) { + if (SlideshowFXServer.getSingleton() != null) { LOGGER.info("Closing the internal server"); SlideshowFXServer.getSingleton().stop(); } @@ -187,14 +203,23 @@ private void stopOSGIManager() { private void stopHostingConnectors() { final List connectors = OSGiManager.getInstance().getInstalledServices(IHostingConnector.class); - if(!connectors.isEmpty()) { + if (!connectors.isEmpty()) { LOGGER.info("Disconnecting from all hosting connectors"); connectors.forEach(hostingConnector -> hostingConnector.disconnect()); } } - public static ReadOnlyObjectProperty stageProperty() { return stage; } - public static Stage getStage() { return stageProperty().get(); } + public static ReadOnlyObjectProperty stageProperty() { + return stage; + } + + public static Stage getStage() { + return stageProperty().get(); + } + + public static SlideshowFXController getMainController() { + return mainController.get(); + } public static void main(String[] args) { SlideshowFX.launch(args); diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/HelpViewController.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/HelpViewController.java index f799be6b..062100bd 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/HelpViewController.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/HelpViewController.java @@ -1,10 +1,9 @@ package com.twasyl.slideshowfx.controllers; -import com.twasyl.slideshowfx.utils.ResourceHelper; +import com.twasyl.slideshowfx.utils.io.IOUtils; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.web.WebView; -import org.asciidoctor.*; import java.net.URL; import java.util.ResourceBundle; @@ -13,7 +12,7 @@ * Controller class of the {@code HelpView.fxml} view. * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.0 */ public class HelpViewController implements Initializable { @@ -38,44 +37,15 @@ protected void loadDeveloperDocumentation() { } protected String getUserDocumentation() { - return this.getDocumentation("/com/twasyl/slideshowfx/documentation/SlideshowFX_user.asciidoc"); + return this.getDocumentation("/com/twasyl/slideshowfx/documentation/html/SlideshowFX_user.html"); } protected String getDeveloperDocumentation() { - return this.getDocumentation("/com/twasyl/slideshowfx/documentation/SlideshowFX_developer.asciidoc"); + return this.getDocumentation("/com/twasyl/slideshowfx/documentation/html/SlideshowFX_developer.html"); } protected String getDocumentation(final String documentationFile) { - final String documentationOriginalContent = ResourceHelper.readResource(documentationFile); - final String documentation = this.getAsciidoctorConverter().convert(documentationOriginalContent, this.getAsciidoctorOptions()); + final String documentation = IOUtils.read(HelpViewController.class.getResourceAsStream(documentationFile)); return documentation; } - - protected Asciidoctor getAsciidoctorConverter() { - final Asciidoctor asciidoctor = Asciidoctor.Factory.create(HelpViewController.class.getClassLoader()); - return asciidoctor; - } - - protected Options getAsciidoctorOptions() { - final Options options = OptionsBuilder.options() - .attributes(getAsciidoctorAttributes()) - .headerFooter(true) - .get(); - - return options; - } - - protected Attributes getAsciidoctorAttributes() { - final Attributes attributes = AttributesBuilder.attributes() - .backend("html5") - .linkCss(false) - .experimental(true) - .tableOfContents(Placement.LEFT) - .styleSheetName("slideshowfx.css") - .stylesDir(ResourceHelper.getExternalForm("/com/twasyl/slideshowfx/documentation/css")) - .noFooter(true) - .get(); - - return attributes; - } } diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/PluginCenterController.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/PluginCenterController.java index 423836f8..1e9ef654 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/PluginCenterController.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controllers/PluginCenterController.java @@ -27,13 +27,13 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.PLUGINS_DIRECTORY; +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.getPluginsDirectory; /** * Controller of the {@code PluginCenter.fxml} view. * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.1 */ public class PluginCenterController implements Initializable { @@ -201,13 +201,13 @@ public void validatePluginsConfiguration() { .forEach(child -> { final PluginFileButton button = (PluginFileButton) child; - if(button.isSelected() && !button.getFile().getParentFile().equals(PLUGINS_DIRECTORY)) { + if(button.isSelected() && !button.getFile().getParentFile().equals(getPluginsDirectory())) { try { OSGiManager.getInstance().deployBundle(button.getFile()); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Can not install plugin: " + button.getFile().getName(), e); } - } else if(!button.isSelected() && button.getFile().getParentFile().equals(PLUGINS_DIRECTORY)) { + } else if(!button.isSelected() && button.getFile().getParentFile().equals(getPluginsDirectory())) { try { OSGiManager.getInstance().uninstallBundle(button.getFile()); } catch (IOException | BundleException e) { diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideDefinition.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideDefinition.java index f75b32fa..708ec36a 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideDefinition.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideDefinition.java @@ -1,9 +1,9 @@ package com.twasyl.slideshowfx.controls.builder.nodes; +import com.twasyl.slideshowfx.icons.FontAwesome; +import com.twasyl.slideshowfx.icons.Icon; import com.twasyl.slideshowfx.ui.controls.ExtendedTextField; import com.twasyl.slideshowfx.utils.DialogHelper; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; @@ -25,7 +25,7 @@ * within the editor. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.3 */ public class SlideDefinition extends TitledPane { @@ -71,7 +71,7 @@ private void initializeMandatoryFields() { private void initializeDeleteButton() { this.delete.getStyleClass().add("delete-slide"); - this.delete.setGraphic(new FontAwesomeIconView(FontAwesomeIcon.TRASH_ALT)); + this.delete.setGraphic(new FontAwesome(Icon.TRASH_ALT)); this.delete.setTooltip(new Tooltip("Delete this slide")); } diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideElementDefinition.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideElementDefinition.java index 23925257..d4abec79 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideElementDefinition.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/SlideElementDefinition.java @@ -1,8 +1,8 @@ package com.twasyl.slideshowfx.controls.builder.nodes; +import com.twasyl.slideshowfx.icons.FontAwesome; +import com.twasyl.slideshowfx.icons.Icon; import com.twasyl.slideshowfx.ui.controls.ExtendedTextField; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; @@ -22,7 +22,7 @@ * Bean providing UI elements used to define a slide element. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.3 */ public class SlideElementDefinition extends FlowPane { @@ -53,7 +53,7 @@ private void initializeMandatoryFields() { private void initializeDeleteButton() { this.delete.getStyleClass().add("delete-slide-element"); - this.delete.setGraphic(new FontAwesomeIconView(FontAwesomeIcon.TRASH_ALT)); + this.delete.setGraphic(new FontAwesome(Icon.TRASH_ALT)); this.delete.setTooltip(new Tooltip("Delete this slide element")); } diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateConfigurationFilePane.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateConfigurationFilePane.java index 55c9bc1b..5976bf4b 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateConfigurationFilePane.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateConfigurationFilePane.java @@ -44,6 +44,7 @@ public class TemplateConfigurationFilePane extends VBox { // General template configuration private ExtendedTextField templateName = new ExtendedTextField("Name", true); + private ExtendedTextField templateVersion = new ExtendedTextField("Version", true); private ExtendedTextField templateFile = new ExtendedTextField("File", true); private ExtendedTextField jsObject = new ExtendedTextField("JS Object", true); private ExtendedTextField templateResourcesDirectory = new ExtendedTextField("Resources' directory", true); @@ -89,6 +90,7 @@ public TemplateConfigurationFilePane() { */ private void initializeMandatoryFields() { this.templateName.setValidator(isNotEmpty()); + this.templateVersion.setValidator(isNotEmpty()); this.templateFile.setValidator(isNotEmpty()); this.templateResourcesDirectory.setValidator(isNotEmpty()); this.jsObject.setValidator(isNotEmpty()); @@ -106,7 +108,7 @@ private void initializeMandatoryFields() { * @return The properly initialized {@link TitledPane} containing the global configuration elements. */ private TitledPane getTemplateGlobalConfigurationPane() { - final FlowPane internalContainer = new FlowPane(5, 5, templateName, templateFile, templateResourcesDirectory, jsObject); + final FlowPane internalContainer = new FlowPane(5, 5, templateName, templateVersion, templateFile, templateResourcesDirectory, jsObject); final TitledPane templateGlobalConfigurationPane = new TitledPane("Template global configuration", internalContainer); templateGlobalConfigurationPane.setCollapsible(false); @@ -237,6 +239,7 @@ private SlideDefinition addSlide() { public String getAsString() { final TemplateConfiguration configuration = new TemplateConfiguration(); configuration.setName(this.templateName.getText()); + configuration.setVersion(this.templateVersion.getText()); configuration.setFile(new File(this.workingPath.toFile(), this.templateFile.getText())); configuration.setJsObject(this.jsObject.getText()); configuration.setResourcesDirectory(new File(this.workingPath.toFile(), this.templateResourcesDirectory.getText())); @@ -308,6 +311,7 @@ public void fillWithFile(final File file) { final TemplateConfiguration configuration = engine.readConfiguration(file); this.templateName.setText(configuration.getName()); + this.templateVersion.setText(configuration.getVersion()); this.templateFile.setText(this.getPathRelativeToWorkingPath(configuration.getFile())); this.jsObject.setText(configuration.getJsObject()); this.templateResourcesDirectory.setText(this.getPathRelativeToWorkingPath(configuration.getResourcesDirectory())); diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateVariable.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateVariable.java index 3acc1489..44c3676f 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateVariable.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/builder/nodes/TemplateVariable.java @@ -1,8 +1,8 @@ package com.twasyl.slideshowfx.controls.builder.nodes; +import com.twasyl.slideshowfx.icons.FontAwesome; +import com.twasyl.slideshowfx.icons.Icon; import com.twasyl.slideshowfx.ui.controls.ExtendedTextField; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; @@ -17,7 +17,7 @@ * Control allowing to define a default template variable. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.3 */ public class TemplateVariable extends FlowPane { @@ -44,7 +44,7 @@ private void initializeMandatoryFields() { private void initializeDeleteButton() { this.delete.getStyleClass().add("delete-default-template-variable"); - this.delete.setGraphic(new FontAwesomeIconView(FontAwesomeIcon.TRASH_ALT)); + this.delete.setGraphic(new FontAwesome(Icon.TRASH_ALT)); this.delete.setTooltip(new Tooltip("Delete this default template variable")); } diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/stages/AboutStage.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/stages/AboutStage.java index 7ed3b745..2ecf6ed1 100644 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/stages/AboutStage.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/stages/AboutStage.java @@ -1,7 +1,6 @@ package com.twasyl.slideshowfx.controls.stages; import com.twasyl.slideshowfx.controllers.AboutViewController; -import com.twasyl.slideshowfx.utils.ResourceHelper; import javafx.scene.paint.Color; import javafx.stage.Modality; import javafx.stage.Stage; @@ -11,13 +10,13 @@ * An implementation of {@link Stage} that displays information about the application. * * @author Thierry Wasylczenko + * @version 1.1 * @since SlideshowFX 1.0 - * @version 1.0.0 */ public class AboutStage extends CustomSlideshowFXStage { public AboutStage() { - super("About", ResourceHelper.getURL("/com/twasyl/slideshowfx/fxml/AboutView.fxml")); + super("About", AboutStage.class.getResource("/com/twasyl/slideshowfx/fxml/AboutView.fxml")); this.initStyle(StageStyle.TRANSPARENT); this.initModality(Modality.APPLICATION_MODAL); this.setAlwaysOnTop(true); diff --git a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/tree/FileTreeCell.java b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/tree/FileTreeCell.java index 7254ce81..89bb7951 100755 --- a/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/tree/FileTreeCell.java +++ b/SlideshowFX-app/src/main/java/com/twasyl/slideshowfx/controls/tree/FileTreeCell.java @@ -1,8 +1,8 @@ package com.twasyl.slideshowfx.controls.tree; +import com.twasyl.slideshowfx.icons.FontAwesome; +import com.twasyl.slideshowfx.icons.Icon; import com.twasyl.slideshowfx.utils.DialogHelper; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.layout.HBox; @@ -19,7 +19,7 @@ * cell. * * @author Thierry Wasylczenko - * @version 1.2 + * @version 1.3 * @since SlideshowFX 1.0 */ public class FileTreeCell extends TreeCell { @@ -69,12 +69,12 @@ protected void initializeGraphic() { if(this.getTreeItem().getValue().isDirectory()) { if (this.getTreeItem().isExpanded()) { - setGraphic(new FontAwesomeIconView(FontAwesomeIcon.FOLDER_OPEN)); + setGraphic(new FontAwesome(Icon.FOLDER_OPEN)); } else { - setGraphic(new FontAwesomeIconView(FontAwesomeIcon.FOLDER)); + setGraphic(new FontAwesome(Icon.FOLDER)); } } else { - setGraphic(new FontAwesomeIconView(FontAwesomeIcon.FILE_TEXT_ALT)); + setGraphic(new FontAwesome(Icon.FILE_TEXT_ALT)); } } else { setGraphic(null); diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/css/information-scene.css b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/css/information-scene.css deleted file mode 100644 index 45cddb64..00000000 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/css/information-scene.css +++ /dev/null @@ -1,8 +0,0 @@ -.information-scene { - -fx-background-color: app-color-background; -} - -.time-elapsed { - -fx-fill: app-color-text-fill; - -fx-font-size: 100px; -} \ No newline at end of file diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/SlideshowFX.fxml b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/SlideshowFX.fxml index f1a2d74c..91aea474 100755 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/SlideshowFX.fxml +++ b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/SlideshowFX.fxml @@ -1,10 +1,9 @@ + - - @@ -14,190 +13,209 @@ fx:controller="com.twasyl.slideshowfx.controllers.SlideshowFXController" minWidth="1024" minHeight="768"> - - + + - - + + - + + + - + + + - + + + - + + + - + + + - + + + - - - - - + + + + + - + + + - - + + + + - - + - + - + - + - - - - + + + + - + - - - - + + + + - + - + - + - - - + + - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -205,113 +223,119 @@ - - - - - - - - - - - + + + + + + + + + + + + - + + + - - - - - + + + + + - + - - - + + + - - + + - - - - - + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -322,10 +346,10 @@ + onDragExited="#dragExitedUI"/> - + \ No newline at end of file diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/TemplateBuilder.fxml b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/TemplateBuilder.fxml index 8fc5d842..92ae8441 100755 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/TemplateBuilder.fxml +++ b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/fxml/TemplateBuilder.fxml @@ -1,8 +1,7 @@ - - + @@ -24,7 +23,7 @@ - + @@ -36,7 +35,7 @@ - + @@ -50,10 +49,10 @@ - - - - + + + + @@ -65,10 +64,10 @@ - - - - + + + + @@ -83,7 +82,7 @@ - + diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/esc_key.png b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/esc_key.png deleted file mode 100644 index 7e2524ac..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/esc_key.png and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/left_key.png b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/left_key.png deleted file mode 100644 index 286aa8b9..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/left_key.png and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/right_key.png b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/right_key.png deleted file mode 100644 index 74864903..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/images/right_key.png and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/sfx-slide-content-editor.zip b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/sfx-slide-content-editor.zip index e7f82b0b..699d128a 100644 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/sfx-slide-content-editor.zip and b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/sfx-slide-content-editor.zip differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/css/font-awesome.min.css b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/css/font-awesome.min.css deleted file mode 100644 index 9b27f8ea..00000000 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/css/font-awesome.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/FontAwesome.otf b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/FontAwesome.otf deleted file mode 100644 index d4de13e8..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/FontAwesome.otf and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.eot b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.eot deleted file mode 100644 index c7b00d2b..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.svg b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.svg deleted file mode 100644 index 8b66187f..00000000 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,685 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.ttf b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.ttf deleted file mode 100644 index f221e50a..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.woff b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.woff deleted file mode 100644 index 6e7483cf..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.woff2 b/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 7eb74fd1..00000000 Binary files a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/webapp/font-awesome/4.6.3/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/SlideshowFX-app/src/test/java/com/twasyl/slideshowfx/controllers/PluginCenterControllerTest.java b/SlideshowFX-app/src/test/java/com/twasyl/slideshowfx/controllers/PluginCenterControllerTest.java index 11d4d9ff..0e23e828 100755 --- a/SlideshowFX-app/src/test/java/com/twasyl/slideshowfx/controllers/PluginCenterControllerTest.java +++ b/SlideshowFX-app/src/test/java/com/twasyl/slideshowfx/controllers/PluginCenterControllerTest.java @@ -1,15 +1,14 @@ package com.twasyl.slideshowfx.controllers; import com.twasyl.slideshowfx.plugin.InstalledPlugin; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileNotFoundException; import java.net.URISyntaxException; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Class testing the {@link PluginCenterController} class. @@ -30,7 +29,7 @@ public class PluginCenterControllerTest { private static final PluginCenterController controller = new PluginCenterController(); - @BeforeClass + @BeforeAll public static void setUp() throws URISyntaxException { FILE_LOCATION = new File(PluginCenterControllerTest.class.getResource("/com/twasyl/slideshowfx/files/plugincenter").toURI()); MISSING_FILE = new File(FILE_LOCATION, "missing.txt"); @@ -41,14 +40,14 @@ public static void setUp() throws URISyntaxException { CORRECT_FILE = new File(FILE_LOCATION, "correct.jar"); } - @Test(expected = NullPointerException.class) + @Test public void fileSeemsInvalidWhenNullFile() throws FileNotFoundException { - assertFalse(controller.fileSeemsValid(null)); + assertThrows(NullPointerException.class, () -> controller.fileSeemsValid(null)); } - @Test(expected = FileNotFoundException.class) + @Test public void fileSeemsInvalidWhenFileNotExists() throws FileNotFoundException { - assertFalse(controller.fileSeemsValid(MISSING_FILE)); + assertThrows(FileNotFoundException.class, () -> controller.fileSeemsValid(MISSING_FILE)); } @Test diff --git a/SlideshowFX-asciidoctor/build.gradle b/SlideshowFX-asciidoctor/build.gradle index db100b1f..e6b2b8a4 100755 --- a/SlideshowFX-asciidoctor/build.gradle +++ b/SlideshowFX-asciidoctor/build.gradle @@ -1,51 +1,29 @@ -description = 'Snippet executor allowing to define slide\'s content using the asciidoctor syntax' -version = '1.0' +description = 'Extension allowing to define slide\'s content using the markdown syntax' +version = '1.1' + +apply plugin: 'java-library' dependencies { - compile project(':SlideshowFX-markup') - compile configurations.felix - compile configurations.asciidoctorj + implementation project(':slideshowfx-markup') - testCompile configurations.junit + compile "org.asciidoctor:asciidoctorj:${rootProject.ext.dependencies.asciidoctorj.version}" + compile 'org.jruby:jruby-complete:1.7.26' } +ext.isPlugin = true +ext.isMarkupPlugin = true +ext.bundle = [ + name : 'SlideshowFX asciidoctor support', + symbolicName : 'com.twasyl.slideshowfx.markup.asciidoctor', + description : 'Support asciidoctor for defining slide\'s content', + activator : 'com.twasyl.slideshowfx.markup.asciidoctor.activator.AsciidoctorActivator', + classPath : configurations.compile.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), + exportPackage : 'com.twasyl.slideshowfx.markup.asciidoctor,com.twasyl.slideshowfx.markup.asciidoctor.activator', + setupWizardLabel: 'Asciidoctor' +] + jar { - from(configurations.asciidoctorj.resolve().collect()) { + from(configurations.compile.resolve().collect()) { include '*' } - - manifest { - attributes('Manifest-Version': '1.0', - 'Bundle-ManifestVersion': '2', - 'Bundle-Name': 'SlideshowFX asciidoctor support', - 'Bundle-SymbolicName': 'com.twasyl.slideshowfx.markup.asciidoctor', - 'Bundle-Description': 'Support asciidoctor for defining slide\'s content', - 'Bundle-Version': "$project.version", - 'Bundle-Activator': 'com.twasyl.slideshowfx.markup.asciidoctor.activator.AsciidoctorActivator', - 'Bundle-ClassPath': configurations.asciidoctorj.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), - 'Bundle-Vendor': 'Thierry Wasylczenko', - 'Export-Package': 'com.twasyl.slideshowfx.markup.asciidoctor,com.twasyl.slideshowfx.markup.asciidoctor.activator', - 'Import-Package': 'org.osgi.framework', - 'Setup-Wizard-Label': 'Asciidoctor') - } -} - -test { - exclude 'com/twasyl/slideshowfx/markup/asciidoctor/**' -} - -bintray { - - configurations = ['archives'] - - pkg { - version { - name = project.version - desc = project.description - released = new Date() - vcsTag = "v${project.version}" - } - } -} - -tasks.bintrayUpload.enabled = asciidoctorMarkupBintrayUploadEnabled \ No newline at end of file +} \ No newline at end of file diff --git a/SlideshowFX-box-hosting-connector/build.gradle b/SlideshowFX-box-hosting-connector/build.gradle old mode 100644 new mode 100755 index c286c567..1ffde274 --- a/SlideshowFX-box-hosting-connector/build.gradle +++ b/SlideshowFX-box-hosting-connector/build.gradle @@ -1,47 +1,30 @@ description = 'Hosting connector allowing to open and save presentations from and to Box' -version = '1.1' +version = '1.2' -dependencies { - compile project(':SlideshowFX-global-configuration') - compile project(':SlideshowFX-hosting-connector') +apply plugin: 'java-library' - compile configurations.felix - compile configurations.box +dependencies { + compile "com.box:box-java-sdk:${rootProject.ext.dependencies.box.version}" + implementation project(':slideshowfx-engines') + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-hosting-connector') + api project(':slideshowfx-plugin') } +ext.isPlugin = true +ext.isHostingConnector = true +ext.bundle = [ + name : 'SlideshowFX Box hosting connector', + symbolicName : 'com.twasyl.slideshowfx.hosting.connector.box', + description : 'Support for connecting to Box', + activator : 'com.twasyl.slideshowfx.hosting.connector.box.activator.BoxHostingConnectorActivator', + classPath : configurations.compile.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), + exportPackage : 'com.twasyl.slideshowfx.hosting.connector.box,com.twasyl.slideshowfx.hosting.connector.box.activator', + setupWizardLabel: 'Box' +] + jar { - from(configurations.box.resolve().collect()) { + from(configurations.compile.resolve().collect()) { include '*' } - - manifest { - attributes('Manifest-Version': '1.0', - 'Bundle-ManifestVersion': '2', - 'Bundle-Name': 'SlideshowFX Box hosting connector', - 'Bundle-SymbolicName': 'com.twasyl.slideshowfx.hosting.connector.box', - 'Bundle-Description': 'Support for connecting to Box', - 'Bundle-Version': version, - 'Bundle-Activator': 'com.twasyl.slideshowfx.hosting.connector.box.activator.BoxHostingConnectorActivator', - 'Bundle-ClassPath': configurations.box.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), - 'Bundle-Vendor': 'Thierry Wasylczenko', - 'Export-Package': 'com.twasyl.slideshowfx.hosting.connector.box,com.twasyl.slideshowfx.hosting.connector.box.activator', - 'Import-Package': 'org.osgi.framework', - 'Setup-Wizard-Label': 'Box') - } -} - -bintray { - - configurations = ['archives'] - - pkg { - version { - name = project.version - desc = project.description - released = new Date() - vcsTag = "v${project.version}" - } - } -} - -tasks.bintrayUpload.enabled = boxHostingConnectorBintrayUploadEnabled \ No newline at end of file +} \ No newline at end of file diff --git a/SlideshowFX-box-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/box/BoxHostingConnector.java b/SlideshowFX-box-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/box/BoxHostingConnector.java old mode 100644 new mode 100755 index b06ee599..a5cf1b3f --- a/SlideshowFX-box-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/box/BoxHostingConnector.java +++ b/SlideshowFX-box-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/box/BoxHostingConnector.java @@ -26,12 +26,13 @@ import java.util.logging.Logger; import static com.twasyl.slideshowfx.engine.presentation.PresentationEngine.DEFAULT_DOTTED_ARCHIVE_EXTENSION; +import static com.twasyl.slideshowfx.hosting.connector.exceptions.HostingConnectorException.NOT_AUTHENTICATED; /** * This connector allows to interact with Box. * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.1 */ public class BoxHostingConnector extends AbstractHostingConnector { @@ -48,38 +49,38 @@ public BoxHostingConnector() { this.setOptions(new BasicHostingConnectorOptions()); String configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(CONSUMER_KEY_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.getOptions().setConsumerKey(configuration.trim()); } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(CONSUMER_SECRET_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.getOptions().setConsumerSecret(configuration.trim()); } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(REDIRECT_URI_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.getOptions().setRedirectUri(configuration.trim()); } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(ACCESS_TOKEN_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.accessToken = configuration; } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(REFRESH_TOKEN_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.refreshToken = configuration; } - if(this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { + if (this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { this.boxApi = new BoxAPIConnection(this.getOptions().getConsumerKey(), this.getOptions().getConsumerSecret()); - if(this.accessToken != null && !this.accessToken.isEmpty()) { + if (this.accessToken != null && !this.accessToken.isEmpty()) { this.boxApi.setAccessToken(this.accessToken); } - if(this.refreshToken != null && !this.refreshToken.isEmpty()) { + if (this.refreshToken != null && !this.refreshToken.isEmpty()) { this.boxApi.setRefreshToken(this.refreshToken); } } @@ -124,7 +125,7 @@ public Node getConfigurationUI() { @Override public void saveNewOptions() { - if(this.getNewOptions() != null) { + if (this.getNewOptions() != null) { this.setOptions(this.getNewOptions()); if (this.getOptions().getConsumerKey() != null) { @@ -142,7 +143,7 @@ public void saveNewOptions() { this.getOptions().getRedirectUri()); } - if(this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { + if (this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { this.boxApi = new BoxAPIConnection(this.getOptions().getConsumerKey(), this.getOptions().getConsumerSecret()); } } @@ -150,7 +151,7 @@ public void saveNewOptions() { @Override public void authenticate() throws HostingConnectorException { - if(this.boxApi == null) throw new HostingConnectorException(HostingConnectorException.MISSING_CONFIGURATION); + if (this.boxApi == null) throw new HostingConnectorException(HostingConnectorException.MISSING_CONFIGURATION); final WebView browser = new WebView(); final Scene scene = new Scene(browser); @@ -159,11 +160,11 @@ public void authenticate() throws HostingConnectorException { browser.setPrefSize(500, 500); browser.getEngine().locationProperty().addListener((locationProperty, oldLocation, newLocation) -> { - if(newLocation != null && newLocation.startsWith(this.getOptions().getRedirectUri())) { + if (newLocation != null && newLocation.startsWith(this.getOptions().getRedirectUri())) { try { final Map uriParameters = getURIParameters(new URI(newLocation)); - if(uriParameters.containsKey("code")) { + if (uriParameters.containsKey("code")) { this.boxApi.authenticate(uriParameters.get("code")); this.accessToken = this.boxApi.getAccessToken(); this.refreshToken = this.boxApi.getRefreshToken(); @@ -188,11 +189,13 @@ public void authenticate() throws HostingConnectorException { stage.setTitle("Authorize SlideshowFX in Box"); stage.showAndWait(); - if(!this.isAuthenticated()) throw new HostingConnectorException(HostingConnectorException.AUTHENTICATION_FAILURE); + if (!this.isAuthenticated()) + throw new HostingConnectorException(HostingConnectorException.AUTHENTICATION_FAILURE); } /** * Get the URL allowing to ask the user the authorization for SlideshowFX to Box. + * * @return The authorization URL. */ protected String getAuthenticationURL() { @@ -209,11 +212,11 @@ protected String getAuthenticationURL() { public boolean checkAccessToken() { boolean valid = false; - if(this.boxApi != null) { + if (this.boxApi != null) { try { BoxUser.getCurrentUser(this.boxApi); valid = true; - } catch(Exception e) { + } catch (Exception e) { LOGGER.log(Level.FINE, "Error when trying to check the access token", e); } } @@ -227,17 +230,24 @@ public void disconnect() { @Override public void upload(PresentationEngine engine, RemoteFile folder, boolean overwrite) throws HostingConnectorException, FileNotFoundException { - if(engine == null) throw new NullPointerException("The engine can not be null"); - if(engine.getArchive() == null) throw new NullPointerException("The archive to upload can not be null"); - if(!engine.getArchive().exists()) throw new FileNotFoundException("The archive to upload does not exist"); - if(!(folder instanceof BoxFile)) throw new IllegalArgumentException("The given folder must be a BoxFile"); + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive to upload can not be null"); + if (!engine.getArchive().exists()) throw new FileNotFoundException("The archive to upload does not exist"); + if (!(folder instanceof BoxFile)) throw new IllegalArgumentException("The given folder must be a BoxFile"); - if(this.isAuthenticated()) { - BoxFolder destination = folder.isRoot() ? BoxFolder.getRootFolder(this.boxApi) : new BoxFolder(this.boxApi, ((BoxFile) folder).getId()); + if (this.isAuthenticated()) { + final BoxFolder destination = folder.isRoot() ? BoxFolder.getRootFolder(this.boxApi) : new BoxFolder(this.boxApi, ((BoxFile) folder).getId()); final FileUploadParams parameters = new FileUploadParams(); + final BoxFile existingFile = (BoxFile) getRemoteFile(engine, folder); + final boolean fileAlreadyExists = existingFile != null; - if(!overwrite && this.fileExists(engine, folder)) { + if (overwrite && fileAlreadyExists) { + final com.box.sdk.BoxFile remoteFile = new com.box.sdk.BoxFile(this.boxApi, existingFile.getId()); + remoteFile.delete(); + + parameters.setName(engine.getArchive().getName()); + } else if (!overwrite && fileAlreadyExists) { final String nameWithoutExtension = engine.getArchive().getName().substring(0, engine.getArchive().getName().lastIndexOf(".")); final Calendar calendar = Calendar.getInstance(); @@ -249,7 +259,7 @@ public void upload(PresentationEngine engine, RemoteFile folder, boolean overwri parameters.setSize(engine.getArchive().length()); parameters.setModified(new Date(System.currentTimeMillis())); - try(final FileInputStream input = new FileInputStream(engine.getArchive())) { + try (final FileInputStream input = new FileInputStream(engine.getArchive())) { parameters.setContent(input); destination.uploadFile(parameters); @@ -257,50 +267,54 @@ public void upload(PresentationEngine engine, RemoteFile folder, boolean overwri LOGGER.log(Level.SEVERE, "Can not upload the presentation", e); } } else { - throw new HostingConnectorException(HostingConnectorException.NOT_AUTHENTICATED); + throw new HostingConnectorException(NOT_AUTHENTICATED); } } @Override public File download(File destination, RemoteFile file) throws HostingConnectorException { - if(destination == null) throw new NullPointerException("The destination can not be null"); - if(file == null) throw new NullPointerException("The file to download can not be null"); - if(!(file instanceof BoxFile)) throw new IllegalArgumentException("The given file must be a BoxFile"); - if(!destination.isDirectory()) throw new IllegalArgumentException("The destination is not a folder"); + if (destination == null) throw new NullPointerException("The destination can not be null"); + if (file == null) throw new NullPointerException("The file to download can not be null"); + if (!(file instanceof BoxFile)) throw new IllegalArgumentException("The given file must be a BoxFile"); + if (!destination.isDirectory()) throw new IllegalArgumentException("The destination is not a folder"); + + File result; - if(this.isAuthenticated()) { + if (this.isAuthenticated()) { final com.box.sdk.BoxFile fileToDownload = new com.box.sdk.BoxFile(this.boxApi, ((BoxFile) file).getId()); + result = new File(destination, file.getName()); - try(final FileOutputStream output = new FileOutputStream(new File(destination, file.getName()))) { + try (final FileOutputStream output = new FileOutputStream(result)) { fileToDownload.download(output); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Can not download the file", e); + result = null; } } else { - throw new HostingConnectorException(HostingConnectorException.NOT_AUTHENTICATED); + throw new HostingConnectorException(NOT_AUTHENTICATED); } - return null; + return result; } @Override public List list(RemoteFile parent, boolean includeFolders, boolean includePresentations) throws HostingConnectorException { - if(parent == null) throw new NullPointerException("The parent can not be null"); - if(!(parent instanceof BoxFile)) throw new IllegalArgumentException("The given parent must be a BoxFile"); + if (parent == null) throw new NullPointerException("The parent can not be null"); + if (!(parent instanceof BoxFile)) throw new IllegalArgumentException("The given parent must be a BoxFile"); final List folders = new ArrayList<>(); - if(this.isAuthenticated()) { + if (this.isAuthenticated()) { final BoxFolder folder = parent.isRoot() ? BoxFolder.getRootFolder(this.boxApi) : new BoxFolder(this.boxApi, ((BoxFile) parent).getId()); folder.forEach(child -> { - if(canFileBeLister(child, includeFolders, includePresentations)) { + if (canFileBeLister(child, includeFolders, includePresentations)) { folders.add(this.createRemoteFile(child, (BoxFile) parent)); } }); } else { - throw new HostingConnectorException(HostingConnectorException.NOT_AUTHENTICATED); + throw new HostingConnectorException(NOT_AUTHENTICATED); } return folders; @@ -308,38 +322,28 @@ public List list(RemoteFile parent, boolean includeFolders, boolean @Override public boolean fileExists(PresentationEngine engine, RemoteFile destination) throws HostingConnectorException { - if(engine == null) throw new NullPointerException("The engine can not be null"); - if(engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); - if(destination == null) throw new NullPointerException("The destination can not be null"); - if(!(destination instanceof BoxFile)) throw new IllegalArgumentException("The given destination must be a BoxFile"); + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); + if (destination == null) throw new NullPointerException("The destination can not be null"); + if (!(destination instanceof BoxFile)) + throw new IllegalArgumentException("The given destination must be a BoxFile"); - boolean exist = false; - - if(this.isAuthenticated()) { - BoxFolder destinationFolder = destination.isRoot() ? BoxFolder.getRootFolder(this.boxApi) : new BoxFolder(this.boxApi, ((BoxFile) destination).getId()); - final Iterator children = destinationFolder.getChildren().iterator(); - - while(!exist && children.hasNext()) { - final BoxItem.Info child = children.next(); - exist = isFile(child) && isNameEqual(child, engine.getArchive().getName()); - } - } else { - throw new HostingConnectorException(HostingConnectorException.NOT_AUTHENTICATED); - } + boolean exist = getRemoteFile(engine, destination) != null; return exist; } /** * Creates an instance of {@link RemoteFile} from a given {@link BoxItem.Info} and a given parent. - * @param info The info to create the remote file for. + * + * @param info The info to create the remote file for. * @param parent The optional parent of the file. * @return A well created {@link RemoteFile} instance. */ protected RemoteFile createRemoteFile(final BoxItem.Info info, final BoxFile parent) { final BoxFile file = new BoxFile(parent, info.getName(), info.getID()); - if(isFile(info)) { + if (isFile(info)) { file.setFile(true); file.setFolder(false); } else { @@ -350,21 +354,54 @@ protected RemoteFile createRemoteFile(final BoxItem.Info info, final BoxFile par return file; } + @Override + public RemoteFile getRemoteFile(final PresentationEngine engine, final RemoteFile destination) throws HostingConnectorException { + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); + if (destination == null) throw new NullPointerException("The destination can not be null"); + if (!(destination instanceof BoxFile)) + throw new IllegalArgumentException("The given destination must be a BoxFile"); + + RemoteFile remoteFile = null; + + if (this.isAuthenticated()) { + final BoxFolder folder = destination.isRoot() ? BoxFolder.getRootFolder(this.boxApi) + : new BoxFolder(this.boxApi, ((BoxFile) destination).getId()); + + final Iterator children = folder.iterator(); + boolean found = false; + + while (!found && children.hasNext()) { + final BoxItem.Info child = children.next(); + found = isFile(child) && isNameEqual(child, engine.getArchive().getName()); + + if (found) { + remoteFile = new BoxFile((BoxFile) destination, engine.getArchive().getName(), child.getID()); + } + } + } else { + throw new HostingConnectorException(NOT_AUTHENTICATED); + } + + return remoteFile; + } + /** * Check if a given {@link BoxItem.Info} can be listed in the UI. - * @param child The info to check. - * @param includeFolders Indicates if the folders are allowed to be listed. + * + * @param child The info to check. + * @param includeFolders Indicates if the folders are allowed to be listed. * @param includePresentations Indicates if the presentations are allowed to be listed. * @return {@code true} if the child can be listed, {@code false} otherwise. */ protected boolean canFileBeLister(BoxItem.Info child, boolean includeFolders, boolean includePresentations) { boolean canBeListed = false; - if(includeFolders && includePresentations) { + if (includeFolders && includePresentations) { canBeListed = isFolder(child) || (isFile(child) && isNameEndingWithSuffix(child, DEFAULT_DOTTED_ARCHIVE_EXTENSION)); - } else if(includeFolders && !includePresentations) { + } else if (includeFolders && !includePresentations) { canBeListed = isFolder(child); - } else if(!includeFolders && includePresentations) { + } else if (!includeFolders && includePresentations) { canBeListed = isFolder(child) && isNameEndingWithSuffix(child, DEFAULT_DOTTED_ARCHIVE_EXTENSION); } @@ -373,6 +410,7 @@ protected boolean canFileBeLister(BoxItem.Info child, boolean includeFolders, bo /** * Check if a given info is considered as a folder or not. + * * @param info The info to check. * @return {@code true} if the info is a folder, {@code false} otherwise. */ @@ -382,6 +420,7 @@ protected boolean isFolder(final BoxItem.Info info) { /** * Check if a given info is considered as a file or not. + * * @param info The info to check. * @return {@code true} if the info is a file, {@code false} otherwise. */ @@ -391,7 +430,8 @@ protected boolean isFile(final BoxItem.Info info) { /** * Check if the name of an info is ending with a given suffix. - * @param info The info to check the name for. + * + * @param info The info to check the name for. * @param suffix The suffix expected at the end of the info's name. * @return {@code true} if the info is ending with the suffix, {@code false} otherwise. */ @@ -401,6 +441,7 @@ protected boolean isNameEndingWithSuffix(final BoxItem.Info info, final String s /** * Check if the name of the info is equal to another name. The check is case sensitive. + * * @param info The info to check the name. * @param name The expected name to be considered equal. * @return {@code true} if the names are equal, {@code false} otherwise. diff --git a/SlideshowFX-code-extension/build.gradle b/SlideshowFX-code-extension/build.gradle index bc4718ae..d34a3735 100755 --- a/SlideshowFX-code-extension/build.gradle +++ b/SlideshowFX-code-extension/build.gradle @@ -1,46 +1,29 @@ description = 'Extension allowing to insert code snippets inside a SlideshowFX presentation' -version = '1.1' +version = '1.2' -dependencies { - compile project(':SlideshowFX-content-extension') - compile configurations.felix - - testCompile configurations.junit - testCompile configurations.mockito - testCompile project(':SlideshowFX-html') - testCompile project(':SlideshowFX-markdown') - testCompile project(':SlideshowFX-textile') -} +apply plugin: 'java-library' -jar { - manifest { - attributes('Manifest-Version': '1.0', - 'Bundle-ManifestVersion': '2', - 'Bundle-Name': 'SlideshowFX code extension', - 'Bundle-SymbolicName': 'com.twasyl.slideshowfx.content.extension.code', - 'Bundle-Description': 'Support for inserting code in slides', - 'Bundle-Version': "$project.version", - 'Bundle-Activator': 'com.twasyl.slideshowfx.content.extension.code.activator.CodeContentExtensionActivator', - 'Bundle-Vendor': 'Thierry Wasylczenko', - 'Export-Package': 'com.twasyl.slideshowfx.content.extension.code,com.twasyl.slideshowfx.content.extension.code.controllers,com.twasyl.slideshowfx.content.extension.code.activator', - 'Import-Package': 'org.osgi.framework', - 'Setup-Wizard-Label': 'Code snippet', - 'Setup-Wizard-Icon-Name': 'CODE') - } -} - -bintray { - - configurations = ['archives'] +dependencies { + api project(':slideshowfx-content-extension') + implementation project(':slideshowfx-icons') + api project(':slideshowfx-markup') + api project(':slideshowfx-plugin') + api project(':slideshowfx-ui-controls') - pkg { - version { - name = project.version - desc = project.description - released = new Date() - vcsTag = "v${project.version}" - } - } + testCompile "org.mockito:mockito-core:${rootProject.ext.dependencies.mockito.version}" + testCompile project(':slideshowfx-html') + testCompile project(':slideshowfx-markdown') + testCompile project(':slideshowfx-textile') } -tasks.bintrayUpload.enabled = codeContentExtensionBintrayUploadEnabled \ No newline at end of file +ext.isPlugin = true +ext.isContentExtension = true +ext.bundle = [ + name : 'SlideshowFX code extension', + symbolicName : 'com.twasyl.slideshowfx.content.extension.code', + description : 'Support for inserting code in slides', + activator : 'com.twasyl.slideshowfx.content.extension.code.activator.CodeContentExtensionActivator', + exportPackage : 'com.twasyl.slideshowfx.content.extension.code,com.twasyl.slideshowfx.content.extension.code.controllers,com.twasyl.slideshowfx.content.extension.code.activator', + setupWizardLabel : 'Code snippet', + setupWizardIconName: 'CODE' +] \ No newline at end of file diff --git a/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/fxml/CodeContentExtension.fxml b/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/fxml/CodeContentExtension.fxml index b441d252..ac7ef653 100755 --- a/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/fxml/CodeContentExtension.fxml +++ b/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/fxml/CodeContentExtension.fxml @@ -4,11 +4,10 @@ - - - + + diff --git a/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/resources/prism.zip b/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/resources/prism.zip index 790b994f..24b5772e 100644 Binary files a/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/resources/prism.zip and b/SlideshowFX-code-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/code/resources/prism.zip differ diff --git a/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/HtmlCodeContentExtensionTest.java b/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/HtmlCodeContentExtensionTest.java index b60e44e3..6c709563 100755 --- a/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/HtmlCodeContentExtensionTest.java +++ b/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/HtmlCodeContentExtensionTest.java @@ -1,16 +1,15 @@ package com.twasyl.slideshowfx.content.extension.code; import com.twasyl.slideshowfx.markup.html.HtmlMarkup; -import com.twasyl.slideshowfx.markup.markdown.MarkdownMarkup; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.twasyl.slideshowfx.content.extension.code.enums.SupportedLanguage.JAVA; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * This class tests the {@link CodeContentExtension} class using a HTML markup. @@ -19,7 +18,7 @@ */ public class HtmlCodeContentExtensionTest extends BaseCodeContentExtensionTest { - @BeforeClass + @BeforeAll public static void setUp() { extension = new CodeContentExtension(); markup = new HtmlMarkup(); diff --git a/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/MarkdownCodeContentExtensionTest.java b/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/MarkdownCodeContentExtensionTest.java index e7b0239b..0fdc58d8 100755 --- a/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/MarkdownCodeContentExtensionTest.java +++ b/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/MarkdownCodeContentExtensionTest.java @@ -1,15 +1,15 @@ package com.twasyl.slideshowfx.content.extension.code; import com.twasyl.slideshowfx.markup.markdown.MarkdownMarkup; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.twasyl.slideshowfx.content.extension.code.enums.SupportedLanguage.JAVA; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * This class tests the {@link CodeContentExtension} class using a markdown markup. @@ -18,7 +18,7 @@ */ public class MarkdownCodeContentExtensionTest extends BaseCodeContentExtensionTest { - @BeforeClass + @BeforeAll public static void setUp() { extension = new CodeContentExtension(); markup = new MarkdownMarkup(); diff --git a/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/TextileCodeContentExtensionTest.java b/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/TextileCodeContentExtensionTest.java index 1c42e0c2..dea60ae4 100755 --- a/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/TextileCodeContentExtensionTest.java +++ b/SlideshowFX-code-extension/src/test/java/com/twasyl/slideshowfx/content/extension/code/TextileCodeContentExtensionTest.java @@ -1,16 +1,15 @@ package com.twasyl.slideshowfx.content.extension.code; import com.twasyl.slideshowfx.markup.textile.TextileMarkup; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.twasyl.slideshowfx.content.extension.code.enums.SupportedLanguage.JAVA; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * This class tests the {@link CodeContentExtension} class. @@ -21,7 +20,7 @@ */ public class TextileCodeContentExtensionTest extends BaseCodeContentExtensionTest { - @BeforeClass + @BeforeAll public static void setUp() { extension = new CodeContentExtension(); markup = new TextileMarkup(); diff --git a/SlideshowFX-content-extension/build.gradle b/SlideshowFX-content-extension/build.gradle index d93eb89d..c87e8ade 100644 --- a/SlideshowFX-content-extension/build.gradle +++ b/SlideshowFX-content-extension/build.gradle @@ -1,11 +1,12 @@ -version = '1.0' +description = 'Module defining how a content extension plugin should be developed' +version = '1.1' -dependencies { - compile project(':SlideshowFX-markup') - compile project(':SlideshowFX-ui-controls') - compile project(':SlideshowFX-utils') - - compile configurations.fontawesomefx -} +apply plugin: 'java-library' -tasks.bintrayUpload.enabled = false \ No newline at end of file +dependencies { + implementation project(':slideshowfx-icons') + implementation project(':slideshowfx-markup') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-ui-controls') + implementation project(':slideshowfx-utils') +} \ No newline at end of file diff --git a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtension.java b/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtension.java index 25e26c70..53737f23 100755 --- a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtension.java +++ b/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtension.java @@ -1,29 +1,33 @@ package com.twasyl.slideshowfx.content.extension; +import com.twasyl.slideshowfx.icons.Icon; import com.twasyl.slideshowfx.plugin.AbstractPlugin; import com.twasyl.slideshowfx.utils.ZipUtils; -import de.jensd.fx.glyphs.GlyphIcons; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.LinkedHashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import static com.twasyl.slideshowfx.content.extension.ResourceLocation.EXTERNAL; + /** * This class defines the basic behavior of a content extension. * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.0 */ public abstract class AbstractContentExtension extends AbstractPlugin implements IContentExtension { private static final Logger LOGGER = Logger.getLogger(AbstractContentExtension.class.getName()); protected final String code; - protected final GlyphIcons icon; + protected final Icon icon; protected final String toolTip; protected final String title; protected final URL resourcesArchive; @@ -40,7 +44,7 @@ public abstract class AbstractContentExtension extends AbstractPlugin implements * @throws NullPointerException If the code is null. * @throws IllegalArgumentException If the code is empty. */ - protected AbstractContentExtension(String code, URL resourcesArchive, GlyphIcons icon, String toolTip, String title) { + protected AbstractContentExtension(String code, URL resourcesArchive, Icon icon, String toolTip, String title) { super(code); if (code == null) throw new NullPointerException("The code of the content extension is null"); @@ -70,9 +74,11 @@ public Set getResources() { } /** - * This method allows to declare resources for this content extension and return this content extension. + * This method allows to declare resources for this content extension and return this content extension. The + * {@link ResourceLocation location} of the resource will be {@link ResourceLocation#INTERNAL}. The resource URL + * will be set to {@code null}. * - * @param type The type of the resource + * @param type The type of the resource. * @param content The content that will be added to the presentation of the resource. * @return This content extension. */ @@ -84,6 +90,24 @@ protected AbstractContentExtension putResource(ResourceType type, String content return this; } + /** + * This method allows to declare resources for this content extension and return this content extension. The + * {@link Resource#getLocation() location} will be set to {@link ResourceLocation#EXTERNAL}. The given URL must + * correspond to a file and not a directory. + * + * @param type The type of the resource. + * @param content The content that will be added to the presentation of the resource. + * @param resourceUrl The {@link URL} of the resource. + * @return This content extension. + */ + protected AbstractContentExtension putResource(ResourceType type, String content, final URL resourceUrl) { + if (content != null && !content.isEmpty() && resourceUrl != null) { + this.resources.add(new Resource(type, content, EXTERNAL, resourceUrl)); + } + + return this; + } + @Override public void extractResources(final File directory) { if (directory == null) @@ -102,10 +126,40 @@ public void extractResources(final File directory) { LOGGER.log(Level.SEVERE, "Can not extract the resources", e); } } + + this.resources.stream() + .filter(resource -> resource.getLocation() == EXTERNAL) + .forEach(resource -> { + final File resourceFile = new File(directory, resource.getContent()); + final File destinationDirectory = resourceFile.getParentFile(); + + boolean destinationDirExists = destinationDirectory.exists(); + if (!destinationDirExists) { + destinationDirExists = destinationDirectory.mkdirs(); + } + + if (destinationDirExists) { + try (final InputStream stream = resource.getResourceUrl().openStream(); + final FileOutputStream output = new FileOutputStream(resourceFile)) { + final byte[] buffer = new byte[512]; + int bytesRead; + + while ((bytesRead = stream.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + } + + output.flush(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can't extract external resource: " + resourceFile.getAbsolutePath(), e); + } + } else { + LOGGER.severe("The destination directory for the external doesn't exist or can't be created: " + destinationDirectory.getAbsolutePath()); + } + }); } @Override - public GlyphIcons getIcon() { + public Icon getIcon() { return this.icon; } diff --git a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtensionController.java b/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtensionController.java deleted file mode 100644 index 6a24db0b..00000000 --- a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/AbstractContentExtensionController.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.twasyl.slideshowfx.content.extension; - -import javafx.application.Platform; -import javafx.fxml.Initializable; - -/** - * @author Thierry Wasylczenko - * @since SlideshowFX - */ -public abstract class AbstractContentExtensionController implements Initializable { - - /** - * Ensure the given action is executed under the FX application thread. This method checks {@link Platform#isFxApplicationThread()} - * in order to determine if the current thread is the FX one. - * @param action The action to run - */ - public void executeUnderFXThread(final Runnable action) { - if(Platform.isFxApplicationThread()) action.run(); - else Platform.runLater(action); - } -} diff --git a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/Resource.java b/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/Resource.java deleted file mode 100644 index a76867d2..00000000 --- a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/Resource.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.twasyl.slideshowfx.content.extension; - -/** - * This class represents a resource of a content extension. It could be a JavaScript file, a CSS file, a JavaScript script - * or a CSS fragment. It has a content and a type. - * - * @author Thierry Wasylczenko - * @version 1.0 - * @since SlideshowFX 1.0 - */ -public class Resource { - private String content; - private ResourceType type; - - public Resource(ResourceType type, String content) { - this.content = content; - this.type = type; - } - - public String getContent() { return content; } - public void setContent(String content) { this.content = content; } - - - public ResourceType getType() { return type; } - public void setType(ResourceType type) { this.type = type; } - - /** - * This method converts the resource as an HTML string. Typically if the type is of {@code ResourceType.JAVASCRIPT_FILE} - * it will produces {@code }. - * @param location The location to include in the {@code src} or {@code href} attribute of the HTML string - * @return The HTML string of the resource. - */ - public String buildHTMLString(String location) { - final StringBuilder builder = new StringBuilder(); - - if(this.getType() == ResourceType.JAVASCRIPT_FILE) { - builder.append(""); - } else if(this.getType() == ResourceType.CSS_FILE) { - builder.append(""); - } else if(this.getType() == ResourceType.CSS) { - builder.append(""); + } + + return builder.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Resource resource = (Resource) o; + return Objects.equals(content, resource.content); + } + + @Override + public int hashCode() { + return Objects.hash(content); + } +} diff --git a/slideshowfx-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceLocation.java b/slideshowfx-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceLocation.java new file mode 100755 index 00000000..249a69a7 --- /dev/null +++ b/slideshowfx-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceLocation.java @@ -0,0 +1,20 @@ +package com.twasyl.slideshowfx.content.extension; + +/** + * Enumeration representing the location of {@link Resource resources} used by a plugin. + * + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 2.0 + */ +public enum ResourceLocation { + /** + * Indicates the {@link Resource} is shipped with the plugin itself. + */ + INTERNAL, + /** + * Indicates the {@link Resource} is shipped outside the plugin itself. It may typically be provided the + * SlideshowFX itself. + */ + EXTERNAL; +} diff --git a/SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceType.java b/slideshowfx-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceType.java similarity index 100% rename from SlideshowFX-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceType.java rename to slideshowfx-content-extension/src/main/java/com/twasyl/slideshowfx/content/extension/ResourceType.java diff --git a/slideshowfx-content-extension/src/test/java/com/twasyl/slideshowfx/content/extension/ResourceTest.java b/slideshowfx-content-extension/src/test/java/com/twasyl/slideshowfx/content/extension/ResourceTest.java new file mode 100644 index 00000000..3109d8b3 --- /dev/null +++ b/slideshowfx-content-extension/src/test/java/com/twasyl/slideshowfx/content/extension/ResourceTest.java @@ -0,0 +1,42 @@ +package com.twasyl.slideshowfx.content.extension; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test for {@link Resource}. + * + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public class ResourceTest { + + @Test + public void testJavaScriptFile() { + final Resource resource = new Resource(ResourceType.JAVASCRIPT_FILE, "somewhere/file.js"); + final String expected = ""; + assertEquals(expected, resource.buildHTMLString("base/path")); + } + + @Test + public void testStyle() { + final Resource resource = new Resource(ResourceType.CSS, "h1 {\n\tcolor: red;\n}"); + final String expected = ""; + assertEquals(expected, resource.buildHTMLString("base/path")); + } +} diff --git a/slideshowfx-documentation/build.gradle b/slideshowfx-documentation/build.gradle new file mode 100755 index 00000000..3304503d --- /dev/null +++ b/slideshowfx-documentation/build.gradle @@ -0,0 +1,106 @@ +buildscript { + dependencies { + classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.15' + } +} + +plugins { + id 'distribution' + id 'org.asciidoctor.convert' version '1.5.6' +} + +description = 'SlideshowFX documentation to be included within the application and the setup package' +version = '1.0' + +asciidoctorj { + version = '1.5.6' +} +asciidoctor { + backends = ['html5', 'pdf'] + attributes = [ + 'source-highlighter' : 'coderay', + 'toc' : 'left', + 'icons' : 'font', + 'setanchors' : '', + 'sectlinks' : '', + 'linkcss' : 'false', + 'slideshowfx_version' : "${rootProject.version}", + 'asciidoctor-source' : "${project.projectDir}/src/asciidoc", + 'javafx-version' : '8 update 162', + 'jdk-version' : '8 update 162', + 'gradle-version' : '4.6', + 'asciidoctorj-version' : "${rootProject.ext.dependencies.asciidoctorj.version}", + 'apache-felix-version' : "${rootProject.ext.dependencies.felix.version}", + 'freemarker-version' : "${rootProject.ext.dependencies.freemarker.version}", + 'jsoup-version' : "${rootProject.ext.dependencies.jsoup.version}", + 'wikitext-textile-core-version': "${rootProject.ext.dependencies.wikitext.version}", + 'txtmark-version' : "${rootProject.ext.dependencies.markdown.version}", + 'vertx-version' : "${rootProject.ext.dependencies.vertx.version}", + 'zxing-jse-version' : "${rootProject.ext.dependencies.zxing.version}", + 'rxJava-version' : "${rootProject.ext.dependencies.rxJava.version}", + 'box-version' : "${rootProject.ext.dependencies.box.version}", + 'drive-version' : "${rootProject.ext.dependencies.drive.version}", + 'dropbox-version' : "${rootProject.ext.dependencies.dropbox.version}", + 'ace-version' : '1.3.1' + ] +} + +build.dependsOn 'asciidoctor' + + +distributions { + html { + baseName = 'slideshowfx-documentation-html' + contents { + into('html') { + from new File(asciidoctor.outputDir, "html5") + } + } + } + pdf { + baseName = 'slideshowfx-documentation-pdf' + contents { + into('pdf') { + from new File(asciidoctor.outputDir, "pdf") + include '*.pdf' + } + } + } +} + +htmlDistZip.dependsOn 'asciidoctor' +pdfDistZip.dependsOn 'asciidoctor' +distZip.dependsOn = [htmlDistZip, pdfDistZip] + +htmlDistTar.enabled = false +pdfDistTar.enabled = false +distTar.enabled = false + +/** + * Unzip all documentation generated by this project. + * + * @param directory The directory where the documentations will be unzipped. + * @param withoutArchiveRootDir Indicates if the root directory present in the archive must be in the direcotry. + * @return + */ +def unzipDocumentationsIn(String directory, boolean withoutArchiveRootDir) { + _unzipDistTask(htmlDistZip, directory, withoutArchiveRootDir) + _unzipDistTask(pdfDistZip, directory, withoutArchiveRootDir) +} + +def _unzipDistTask(Task task, String directory, boolean withoutArchiveRootDir) { + task.outputs.files.each { file -> + copy { + from zipTree(file) + into directory + } + } + + if (withoutArchiveRootDir) { + copy { + from fileTree(dir: "${directory}/${task.baseName}-${version}") + into directory + } + delete "${directory}/${task.baseName}-${version}" + } +} \ No newline at end of file diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/SlideshowFX_developer.asciidoc b/slideshowfx-documentation/src/docs/asciidoc/SlideshowFX_developer.asciidoc similarity index 88% rename from SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/SlideshowFX_developer.asciidoc rename to slideshowfx-documentation/src/docs/asciidoc/SlideshowFX_developer.asciidoc index 9e178ddf..8c74353c 100755 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/SlideshowFX_developer.asciidoc +++ b/slideshowfx-documentation/src/docs/asciidoc/SlideshowFX_developer.asciidoc @@ -12,7 +12,7 @@ But SlideshowFX isn't just another simple presentation engine. It brings much mo - A chat is provided so your audience can send you questions using their smartphones and you will answer them whenever you want ; - A quiz creator so your audience can answer your quiz using their smartphones and you see results live ; -- Save your presentations on Dropbox or Google Drive ; +- Save your presentations on Box, Dropbox or Google Drive ; - Insert code snippets that can be executed directly from the presentation and console's output is displayed in the slide. == Developer guide @@ -29,24 +29,18 @@ But SlideshowFX isn't just another simple presentation engine. It brings much mo | Apache Felix | {apache-felix-version} | -| FontAwesomeFX | {fontawesomefx-version} | - | freemarker | {freemarker-version} | | jsoup | {jsoup-version} | -| org.eclipse.mylyn.wikitext.core | {wikitext-core-version} | Library used for textile support - | org.eclipse.mylyn.wikitext.textile.core | {wikitext-textile-core-version} | Library used for textile support -| twitter4j | {twitter4j-version} | +| RxJava | {rxJava-version} | | txtmark | {txtmark-version} | Library used for the markdown support | vertx-core | {vertx-version} | -| zxing-core | {zxing-core-version} | - | zxing-jse | {zxing-jse-version} | | http://ace.c9.io[ACE] | {ace-version} | @@ -58,6 +52,7 @@ But SlideshowFX isn't just another simple presentation engine. It brings much mo SlideshowFX contains the following modules: - *SlideshowFX-app* which is the module for the desktop client of SlideshowFX ; +- *SlideshowFX-documentation* which contains the documentation of the application ; - *SlideshowFX-utils* which defines utilities classes that may be used in other modules ; - *SlideshowFX-engines* which defines the engines that are used in SlideshowFX for working with templates and presentations ; - *SlideshowFX-controls* which defines the custom graphical controls that may be used in other modules ; @@ -78,6 +73,7 @@ SlideshowFX contains the following modules: - *SlideshowFX-quiz-extension* which defines the implementation of SlideshowFX-content-extension to insert quiz in slides ; - *SlideshowFX-quote-extension* which defines the implementation of SlideshowFX-content-extension to insert quotes in slides ; - *SlideshowFX-sequence-diagram-extension* which defines the implementation of SlideshowFX-content-extension to insert sequence diagrams in slides ; +- *SlideshowFX-shape-extension* which defines the implementation of SlideshowFX-content-extension to insert shapes in slides ; - *SlideshowFX-snippet-extension* which defines the implementation of SlideshowFX-content-extension to insert executable code snippet in slides ; - *SlideshowFX-hosting-connector* which defines the base classes for creating an OSGi module for connecting to a file hosting service ; - *SlideshowFX-box-hosting-connector* which defines the implementation of SlideshowFX-hosting-connector to connect to Box ; @@ -94,42 +90,45 @@ SlideshowFX contains the following modules: === Gradle -SlideshowFX uses http://www.gradle.org/[gradle] as build system. The version used is {gradle-version}. + -The following plugins are currently used in the Gradle script: +SlideshowFX uses https://gradle.org/[gradle] as build system. The version used is {gradle-version}. + +==== Particularities -- bintray -- java -- asciidoctor +The documentation provided within the SlideshowFX application is the same as the one provided by the setup. In order to avoid duplicate files, the `slideshowfx-app` module unpacks the `slideshowfx-documentation` HTML documentation in its own sources. In order to update the documentation within the application and display it in the application during development, the following command could be used: + +[source] +---- +gradlew :slideshowfx-app:processResources +---- -==== Tasks +==== Custom tasks -In order to build SlideshowFX, some gradle tasks have been created: +Some gradle tasks are provided to help the developer : -- `buildJavaFXBundle` which depends on the JAR task has been created in the `SlideshowFX-app` project. This task creates the JavaFX bundle ; -- `installMarkupPlugins` (in the root project) which copies the markup supported to the directory of plugins ; -- `installContentExtensionPlugins` (in the root project) which copies the content extensions to the directory of plugins ; -- `installHostingConnectorPlugins` (in the root project) which copies the hosting connectors to the directory of plugins ; -- `installSnippetExecutor` (in the root project) which copies the snippet executors to the directory of plugins ; -- `installAllPlugins` (in the root project) which copies all plugins to the directory of plugins ; -- `packageSlideshowFX` (in the root project) which packages the application, markups, extensions and documentation together. +- *installPlugin* provided for all plugins and allowing to deploy the plugin directly to the folder : `~/.SlideshowFX/plugins` ; +- *listSnapshotProjects* allowing to list projects having a `SNAPSHOT` version. This is particularly useful for knowing which project should update it's version before publishing ; +- *saveProjectsToBePublished* allowing to save within the `gradle.properties` file the projects having a `SNAPSHOT` version and that should be published on bintray. === Set up your environment ==== Environment variable -In order to build SlideshowFX, you will need to set *JAVA_HOME* to point to your JDK {jdk-version} installation and *GRADLE_HOME* to your gradle installation. Ensure both variables are present in the *PATH* environment variable. +In order to build SlideshowFX, you will need to set *JAVA_HOME* to point to your JDK {jdk-version} installation. Ensure the variable the present in the *PATH* environment variable. ==== Running SlideshowFX in your IDE If you are contributing to SlideshowFX and developing some features, you probably use an IDE (http://www.jetbrains.com/idea/[IntelliJ IDEA], https://netbeans.org/[NetBeans], http://www.eclipse.org/[eclipse], ...). + -=== Templates +In order to start the application from your IDE, you can start the `com.twasyl.slideshowfx.app.SlideshowFX` class. + +=== Creating templates Each presentation done with SlideshowFX is based on a _template_. + -A template is composed by three main parts: +A template is composed by four main parts: - A _template configuration_ file which contains the configuration of the template. This file *must be* named *template-config.json* and is written using JSON ; - A _template file_ which is the HTML page that will host all slides of the presentation ; +- A _sample file_ which is an HTML page providing a sample of the template ; - _Slide's template files_ which are the template for each kind of slide the user can add in his presentation. All of this content is archived in a file with the extension *.sfxt* (which stands for SlideshowFX template) @@ -140,6 +139,7 @@ A typical template archive structure is the following: / |- [F] template-config.json |- [F] template.html +|- [F] sample.html |- [D] resources |- [D] slides |------|- [D] template @@ -158,6 +158,7 @@ The template configuration must be at the root of the archive and will contain a { "template" : { "name": "My first template", + "version" : "0.2", "file" : "template.html", "js-object" : "sfx", "resources-directory" : "resources", @@ -255,6 +256,7 @@ The template configuration must be at the root of the archive and will contain a The complete configuration is wrapped into a *template* JSON object. This object is described as below: * *name* : the name of the template ; +* *version* : the version of the template ; * *file* : the HTML file that is the template, which will host the slides ; * *js-object* : is the name JavaScript object that will be used to callback to SlideshowFX ; * *slides-container* : is the ID of the HTML markup that will contain the slides ; @@ -281,6 +283,8 @@ The complete configuration is wrapped into a *template* JSON object. This object **** *template-expression* : the name of the template token. It is the Velocity token without the dollar sign ; **** *prompt-message* : the message displayed to the user asking the value of the attribute. +NOTE: The best way for creating the template configuration file is to use the editor available in SlideshowFX. + ==== Template file The template file is the file that will host all slides, include all JavaScript libraries, CSS files and so on. In order to work, you have to: @@ -332,6 +336,12 @@ The template of a slide will define what HTML element a slide is. In some framew [source,html]

- Each element that is editable by the user must have the *ondblclick* attribute set to *$\{sfxCallback\}* +- It is strongly recommended to listen to slide changed event in the template file. When such an event is fired, you can call notify the SlideshowFX browser with the new current slide ID: +[source,html] +---- +var slide = ...; // i.e.: document.getElementById(...) +sfxBrowser.fireSlideChangedEvent(slide.id); +---- - If dynamic attributes are needed, they can be defined like the following. Note that for this example, template-expression are *slideDataX* and *slideDataY* [source,html]
@@ -460,4 +470,6 @@ The *presentation* JSON object is described below: *** *element-id* : the ID of the slide element ; *** *original-content-code* : the code of the markup syntax used ; *** *original-content* : the original content of the element encoded in Base64. This syntax of the content must correspond to the content code ; -*** *html-content* : the original content converted in HTML encoded in Base64. \ No newline at end of file +*** *html-content* : the original content converted in HTML encoded in Base64. + +NOTE: This file shouldn't be modified manually as it is generated and overwritten by the application. \ No newline at end of file diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/SlideshowFX_user.asciidoc b/slideshowfx-documentation/src/docs/asciidoc/SlideshowFX_user.asciidoc similarity index 95% rename from SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/SlideshowFX_user.asciidoc rename to slideshowfx-documentation/src/docs/asciidoc/SlideshowFX_user.asciidoc index b0b85a90..09b90971 100755 --- a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/SlideshowFX_user.asciidoc +++ b/slideshowfx-documentation/src/docs/asciidoc/SlideshowFX_user.asciidoc @@ -45,7 +45,9 @@ In order to create a new presentation in SlideshowFX, you can: * click on the *New presentation* icon (icon:file[]) in the tool bar ; * press kbd:[Ctrl+N] on Windows or kbd:[Cmd+N] on OSX. -Then you will have to choose a SlideshowFX template and confirm your choice. +In the new window, you can either choose a template stored within your template's library or import a new template from your disk by clicking on the *Import new template* button. The imported template will be displayed in the template's library but will not be imported until a presentation is created with it. + +When choosing a template, if it provides a preview, it will be displayed on the right side of the window. === Creating slides @@ -101,14 +103,17 @@ When you have entered the slideshow mode and want to exit it, press kbd:[Escape] SlideshowFX provides some options that can be changed using the *Options* menu and it's *Options* item. -=== Embeded server - -SlideshowFX embeds a server that allows the audience to chat with the presenter and to answer quizs started by this same presenter. You are able to: +You are able to: * Enable or disable an automatic save of your work at a given interval ; * Define the interval (in minutes) of saving automatically your work ; * Enable or disable the temporary files SlideshowFX creates ; -* Define the max age (in days) of temporary files to be deleted. Files older of than this age will be removed when SlideshowFX closes if the parameter is enabled. +* Define the max age (in days) of temporary files to be deleted. Files older of than this age will be removed when SlideshowFX closes if the parameter is enabled ; +* Define how many presentations are suggested for the *Open recent* menu. + +=== Embeded server + +SlideshowFX embeds a server that allows the audience to chat with the presenter and to answer quizs started by this same presenter. ==== Starting the server @@ -180,7 +185,8 @@ Content extension plugins allow to facilitate the insertion of specific elements * JavaScript sweet alerts ; * quotes ; * quizs ; -* sequence diagrams. +* sequence diagram ; +* shapes. When you click on the button of one of these plugins in the tool bar next to the editing area and then validate it's dialog, the plugin will insert the proper content inside the editing area, according the markup syntax you have chosen. If the plugin doesn't support the chosen syntax, HTML will be generated instead. diff --git a/SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/css/slideshowfx.css b/slideshowfx-documentation/src/docs/asciidoc/css/slideshowfx.css similarity index 100% rename from SlideshowFX-app/src/main/resources/com/twasyl/slideshowfx/documentation/css/slideshowfx.css rename to slideshowfx-documentation/src/docs/asciidoc/css/slideshowfx.css diff --git a/src/docs/asciidoc/install.asciidoc b/slideshowfx-documentation/src/docs/asciidoc/install.asciidoc similarity index 100% rename from src/docs/asciidoc/install.asciidoc rename to slideshowfx-documentation/src/docs/asciidoc/install.asciidoc diff --git a/slideshowfx-drive-hosting-connector/build.gradle b/slideshowfx-drive-hosting-connector/build.gradle new file mode 100755 index 00000000..46abac22 --- /dev/null +++ b/slideshowfx-drive-hosting-connector/build.gradle @@ -0,0 +1,30 @@ +description = 'Hosting connector allowing to open and save presentations from and to Google Drive' +version = '1.3' + +apply plugin: 'java-library' + +dependencies { + compile "com.google.apis:google-api-services-drive:${rootProject.ext.dependencies.drive.version}" + implementation project(':slideshowfx-engines') + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-hosting-connector') + api project(':slideshowfx-plugin') +} + +ext.isPlugin = true +ext.isHostingConnector = true +ext.bundle = [ + name : 'SlideshowFX Google Drive hosting connector', + symbolicName : 'com.twasyl.slideshowfx.hosting.connector.drive', + description : 'Support for connecting to Google Drive', + activator : 'com.twasyl.slideshowfx.hosting.connector.drive.activator.DriveHostingConnectorActivator', + classPath : configurations.compile.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), + exportPackage : 'com.twasyl.slideshowfx.hosting.connector.drive,com.twasyl.slideshowfx.hosting.connector.drive.activator', + setupWizardLabel: 'Google Drive' +] + +jar { + from(configurations.compile.resolve().collect()) { + include '*' + } +} \ No newline at end of file diff --git a/SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/DriveHostingConnector.java b/slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/DriveHostingConnector.java old mode 100644 new mode 100755 similarity index 95% rename from SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/DriveHostingConnector.java rename to slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/DriveHostingConnector.java index 8930f221..27ada1e3 --- a/SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/DriveHostingConnector.java +++ b/slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/DriveHostingConnector.java @@ -40,7 +40,7 @@ * This connector allows to interact with Google Drive. * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.0 */ public class DriveHostingConnector extends AbstractHostingConnector { @@ -360,15 +360,32 @@ public boolean fileExists(PresentationEngine engine, RemoteFile destination) thr if (!(destination instanceof GoogleFile)) throw new IllegalArgumentException("The given destination must be a GoogleFile"); - boolean exists; + boolean exists = this.getRemoteFile(engine, destination) != null; + + return exists; + } + + @Override + public RemoteFile getRemoteFile(PresentationEngine engine, RemoteFile destination) throws HostingConnectorException { + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); + if (destination == null) throw new NullPointerException("The destination can not be null"); + if (!(destination instanceof GoogleFile)) + throw new IllegalArgumentException("The given destination must be a GoogleFile"); + + RemoteFile remoteFile = null; if (this.isAuthenticated()) { - exists = getFile(engine, destination) != null; + final com.google.api.services.drive.model.File file = getFile(engine, destination); + + if (file != null) { + remoteFile = new GoogleFile((GoogleFile) destination, file.getName(), file.getId()); + } } else { throw new HostingConnectorException(HostingConnectorException.NOT_AUTHENTICATED); } - return exists; + return remoteFile; } /** diff --git a/SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/activator/DriveHostingConnectorActivator.java b/slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/activator/DriveHostingConnectorActivator.java similarity index 100% rename from SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/activator/DriveHostingConnectorActivator.java rename to slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/activator/DriveHostingConnectorActivator.java diff --git a/SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/io/GoogleFile.java b/slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/io/GoogleFile.java similarity index 100% rename from SlideshowFX-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/io/GoogleFile.java rename to slideshowfx-drive-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/drive/io/GoogleFile.java diff --git a/SlideshowFX-drive-hosting-connector/src/main/resources/META-INF/icon.png b/slideshowfx-drive-hosting-connector/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-drive-hosting-connector/src/main/resources/META-INF/icon.png rename to slideshowfx-drive-hosting-connector/src/main/resources/META-INF/icon.png diff --git a/slideshowfx-dropbox-hosting-connector/build.gradle b/slideshowfx-dropbox-hosting-connector/build.gradle new file mode 100755 index 00000000..44d243cb --- /dev/null +++ b/slideshowfx-dropbox-hosting-connector/build.gradle @@ -0,0 +1,30 @@ +description = 'Hosting connector allowing to open and save presentations from and to Dropbox' +version = '1.2' + +apply plugin: 'java-library' + +dependencies { + compile "com.dropbox.core:dropbox-core-sdk:${rootProject.ext.dependencies.dropbox.version}" + implementation project(':slideshowfx-engines') + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-hosting-connector') + api project(':slideshowfx-plugin') +} + +ext.isPlugin = true +ext.isHostingConnector = true +ext.bundle = [ + name : 'SlideshowFX Dropbox hosting connector', + symbolicName : 'com.twasyl.slideshowfx.hosting.connector.dropbox', + description : 'Support for connecting to Dropbox', + activator : 'com.twasyl.slideshowfx.hosting.connector.dropbox.activator.DropboxHostingConnectorActivator', + classPath : configurations.compile.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), + exportPackage : 'com.twasyl.slideshowfx.hosting.connector.dropbox,com.twasyl.slideshowfx.hosting.connector.dropbox.activator', + setupWizardLabel: 'Dropbox' +] + +jar { + from(configurations.compile.resolve().collect()) { + include '*' + } +} \ No newline at end of file diff --git a/SlideshowFX-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/DropboxHostingConnector.java b/slideshowfx-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/DropboxHostingConnector.java old mode 100644 new mode 100755 similarity index 77% rename from SlideshowFX-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/DropboxHostingConnector.java rename to slideshowfx-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/DropboxHostingConnector.java index 7502bd81..5173a769 --- a/SlideshowFX-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/DropboxHostingConnector.java +++ b/slideshowfx-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/DropboxHostingConnector.java @@ -35,14 +35,16 @@ * This connector allows to interact with Dropbox. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class DropboxHostingConnector extends AbstractHostingConnector { private static final Logger LOGGER = Logger.getLogger(DropboxHostingConnector.class.getName()); private DbxAppInfo appInfo; - private final DbxRequestConfig dropboxConfiguration = new DbxRequestConfig("SlideshowFX", Locale.getDefault().toString()); + private final DbxRequestConfig dropboxConfiguration = DbxRequestConfig.newBuilder("SlideshowFX") + .withUserLocaleFrom(Locale.getDefault()) + .build(); public DropboxHostingConnector() { super("dropbox", "Dropbox", new RemoteFile(null)); @@ -50,26 +52,26 @@ public DropboxHostingConnector() { this.setOptions(new BasicHostingConnectorOptions()); String configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(CONSUMER_KEY_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.getOptions().setConsumerKey(configuration.trim()); } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(CONSUMER_SECRET_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.getOptions().setConsumerSecret(configuration.trim()); } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(REDIRECT_URI_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.getOptions().setRedirectUri(configuration.trim()); } configuration = GlobalConfiguration.getProperty(getConfigurationBaseName().concat(ACCESS_TOKEN_PROPERTY_SUFFIX)); - if(configuration != null && !configuration.trim().isEmpty()) { + if (configuration != null && !configuration.trim().isEmpty()) { this.accessToken = configuration; } - if(this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { + if (this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { this.appInfo = new DbxAppInfo(this.getOptions().getConsumerKey(), this.getOptions().getConsumerSecret()); } } @@ -113,7 +115,7 @@ public Node getConfigurationUI() { @Override public void saveNewOptions() { - if(this.getNewOptions() != null) { + if (this.getNewOptions() != null) { this.setOptions(this.getNewOptions()); if (this.getOptions().getConsumerKey() != null) { @@ -131,7 +133,7 @@ public void saveNewOptions() { this.getOptions().getRedirectUri()); } - if(this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { + if (this.getOptions().getConsumerKey() != null && this.getOptions().getConsumerSecret() != null) { this.appInfo = new DbxAppInfo(this.getOptions().getConsumerKey(), this.getOptions().getConsumerSecret()); } } @@ -139,10 +141,14 @@ public void saveNewOptions() { @Override public void authenticate() throws HostingConnectorException { - if(this.appInfo == null) throw new HostingConnectorException(HostingConnectorException.MISSING_CONFIGURATION); + if (this.appInfo == null) throw new HostingConnectorException(HostingConnectorException.MISSING_CONFIGURATION); // Prepare the request - final DbxWebAuthNoRedirect authentication = new DbxWebAuthNoRedirect(this.dropboxConfiguration, this.appInfo); + final DbxWebAuth authentication = new DbxWebAuth(this.dropboxConfiguration, this.appInfo); + final DbxWebAuth.Request request = DbxWebAuth.Request.newBuilder() + .withNoRedirect() + .build(); + final String authorizationUrl = authentication.authorize(request); final WebView browser = new WebView(); final Scene scene = new Scene(browser); @@ -161,7 +167,7 @@ public void authenticate() throws HostingConnectorException { if (attributes != null && (dataToken = attributes.getNamedItem("data-token").getTextContent()) != null) { try { - final DbxAuthFinish authenticationFinish = authentication.finish(dataToken); + final DbxAuthFinish authenticationFinish = authentication.finishFromCode(dataToken); this.accessToken = authenticationFinish.getAccessToken(); } catch (DbxException e) { LOGGER.log(Level.SEVERE, "Can not finish authentication", e); @@ -178,13 +184,14 @@ public void authenticate() throws HostingConnectorException { } }); - browser.getEngine().load(authentication.start()); + browser.getEngine().load(authorizationUrl); stage.setScene(scene); stage.setTitle("Authorize SlideshowFX in Dropbox"); stage.showAndWait(); - if(!this.isAuthenticated()) throw new HostingConnectorException(HostingConnectorException.AUTHENTICATION_FAILURE); + if (!this.isAuthenticated()) + throw new HostingConnectorException(HostingConnectorException.AUTHENTICATION_FAILURE); } @Override @@ -208,25 +215,23 @@ public void disconnect() { @Override public void upload(PresentationEngine engine, RemoteFile folder, boolean overwrite) throws HostingConnectorException, FileNotFoundException { - if(engine == null) throw new NullPointerException("The engine can not be null"); - if(engine.getArchive() == null) throw new NullPointerException("The archive to upload can not be null"); - if(!engine.getArchive().exists()) throw new FileNotFoundException("The archive to upload does not exist"); + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive to upload can not be null"); + if (!engine.getArchive().exists()) throw new FileNotFoundException("The archive to upload does not exist"); - if(this.isAuthenticated()) { + if (this.isAuthenticated()) { final String computedName = folder.toString().concat("/".concat(engine.getArchive().getName())); final DbxClientV2 client = new DbxClientV2(this.dropboxConfiguration, this.accessToken); WriteMode writeMode; - final StringBuilder fileName = new StringBuilder(); - final UploadBuilder uploader = client.files().uploadBuilder(computedName); - if(overwrite) { + if (overwrite) { try { final Metadata metadata = client.files().getMetadata(computedName); // Ensure the file has been found. - if(metadata != null) { + if (metadata != null) { writeMode = WriteMode.OVERWRITE; uploader.withAutorename(true); } else { @@ -243,7 +248,7 @@ public void upload(PresentationEngine engine, RemoteFile folder, boolean overwri uploader.withMode(writeMode); - try(final InputStream archiveStream = new FileInputStream(engine.getArchive())) { + try (final InputStream archiveStream = new FileInputStream(engine.getArchive())) { uploader.start().uploadAndFinish(archiveStream); } catch (DbxException | IOException e) { LOGGER.log(Level.SEVERE, "Error while trying to upload the presentation", e); @@ -255,16 +260,16 @@ public void upload(PresentationEngine engine, RemoteFile folder, boolean overwri @Override public File download(File destination, RemoteFile file) throws HostingConnectorException { - if(destination == null) throw new NullPointerException("The destination can not be null"); - if(file == null) throw new NullPointerException("The file to download can not be null"); - if(!destination.isDirectory()) throw new IllegalArgumentException("The destination is not a folder"); + if (destination == null) throw new NullPointerException("The destination can not be null"); + if (file == null) throw new NullPointerException("The file to download can not be null"); + if (!destination.isDirectory()) throw new IllegalArgumentException("The destination is not a folder"); File result; - if(this.isAuthenticated()) { + if (this.isAuthenticated()) { result = new File(destination, file.getName()); - try(final OutputStream out = new FileOutputStream(result)) { + try (final OutputStream out = new FileOutputStream(result)) { final DbxClientV2 client = new DbxClientV2(this.dropboxConfiguration, this.accessToken); client.files().download(file.toString()).download(out); @@ -282,31 +287,31 @@ public File download(File destination, RemoteFile file) throws HostingConnectorE @Override public List list(RemoteFile parent, boolean includeFolders, boolean includePresentations) throws HostingConnectorException { - if(parent == null) throw new NullPointerException("The parent can not be null"); + if (parent == null) throw new NullPointerException("The parent can not be null"); final List folders = new ArrayList<>(); - if(this.isAuthenticated()) { + if (this.isAuthenticated()) { final DbxClientV2 client = new DbxClientV2(this.dropboxConfiguration, this.accessToken); final ListFolderResult listing; try { listing = client.files().listFolderBuilder(parent.isRoot() ? "" : parent.toString()) - .withRecursive(false) - .withIncludeDeleted(false) - .start(); + .withRecursive(false) + .withIncludeDeleted(false) + .start(); listing.getEntries() .stream() .filter(entry -> { - if(includeFolders && includePresentations) { + if (includeFolders && includePresentations) { return isFolder(entry) || (isFile(entry) && isNameEndingWithSuffix(entry, DEFAULT_DOTTED_ARCHIVE_EXTENSION)); - } else if(includeFolders && !includePresentations) { + } else if (includeFolders && !includePresentations) { return isFolder(entry); - } else if(!includeFolders && includePresentations) { + } else if (!includeFolders && includePresentations) { return isFolder(entry) && isNameEndingWithSuffix(entry, DEFAULT_DOTTED_ARCHIVE_EXTENSION); } else return false; }) - .forEach(entry -> folders.add(this.createRemoteFile(entry, parent))); + .forEach(entry -> folders.add(this.createRemoteFile(entry, parent))); } catch (DbxException e) { LOGGER.log(Level.SEVERE, "Error while retrieving the folders", e); } @@ -319,13 +324,14 @@ public List list(RemoteFile parent, boolean includeFolders, boolean /** * Creates an instance of {@link RemoteFile} from a given {@link Metadata} and a given parent. + * * @param metadata The metadata to create the remote file for. - * @param parent The optional parent of the file. + * @param parent The optional parent of the file. * @return A well created {@link RemoteFile} instance. */ protected RemoteFile createRemoteFile(final Metadata metadata, final RemoteFile parent) { final RemoteFile file = new RemoteFile(parent, metadata.getName()); - if(isFile(metadata)) { + if (isFile(metadata)) { file.setFile(true); file.setFolder(false); } else { @@ -338,32 +344,43 @@ protected RemoteFile createRemoteFile(final Metadata metadata, final RemoteFile @Override public boolean fileExists(PresentationEngine engine, RemoteFile destination) throws HostingConnectorException { - if(engine == null) throw new NullPointerException("The engine can not be null"); - if(engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); - if(destination == null) throw new NullPointerException("The destination can not be null"); + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); + if (destination == null) throw new NullPointerException("The destination can not be null"); + + boolean exist = this.getRemoteFile(engine, destination) != null; + + return exist; + } + + @Override + public RemoteFile getRemoteFile(PresentationEngine engine, RemoteFile destination) throws HostingConnectorException { + if (engine == null) throw new NullPointerException("The engine can not be null"); + if (engine.getArchive() == null) throw new NullPointerException("The archive file can not be null"); + if (destination == null) throw new NullPointerException("The destination can not be null"); - boolean exist; + RemoteFile remoteFile = null; - if(this.isAuthenticated()) { + if (this.isAuthenticated()) { final DbxClientV2 client = new DbxClientV2(this.dropboxConfiguration, this.accessToken); final RemoteFile remotePresentation = new RemoteFile(destination, engine.getArchive().getName()); try { client.files().getMetadata(remotePresentation.toString()); - exist = true; + remoteFile = remotePresentation; } catch (DbxException e) { LOGGER.log(Level.FINE, "The presentation hasn't been found remotely", e); - exist = false; } } else { throw new HostingConnectorException(HostingConnectorException.NOT_AUTHENTICATED); } - return exist; + return remoteFile; } /** * Check if a given metadata is considered as a folder or not. + * * @param metadata The metadata to check. * @return {@code true} if the metadata is a folder, {@code false} otherwise. */ @@ -373,6 +390,7 @@ protected boolean isFolder(final Metadata metadata) { /** * Check if a given metadata is considered as a file or not. + * * @param metadata The metadata to check. * @return {@code true} if the metadata is a file, {@code false} otherwise. */ @@ -382,8 +400,9 @@ protected boolean isFile(final Metadata metadata) { /** * Check if the name of a metada is ending with a given suffix. + * * @param metadata The metadata to check the name for. - * @param suffix The suffix expected at the end of the metadata's name. + * @param suffix The suffix expected at the end of the metadata's name. * @return {@code true} if the metadata is ending with the suffix, {@code false} otherwise. */ protected boolean isNameEndingWithSuffix(final Metadata metadata, final String suffix) { @@ -392,8 +411,9 @@ protected boolean isNameEndingWithSuffix(final Metadata metadata, final String s /** * Check if the name of the metadata is equal to another name. The check is case sensitive. + * * @param metadata The metadata to check the name. - * @param name The expected name to be considered equal. + * @param name The expected name to be considered equal. * @return {@code true} if the names are equal, {@code false} otherwise. */ protected boolean isNameEqual(final Metadata metadata, final String name) { diff --git a/SlideshowFX-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/activator/DropboxHostingConnectorActivator.java b/slideshowfx-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/activator/DropboxHostingConnectorActivator.java similarity index 100% rename from SlideshowFX-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/activator/DropboxHostingConnectorActivator.java rename to slideshowfx-dropbox-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/dropbox/activator/DropboxHostingConnectorActivator.java diff --git a/SlideshowFX-dropbox-hosting-connector/src/main/resources/META-INF/icon.png b/slideshowfx-dropbox-hosting-connector/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-dropbox-hosting-connector/src/main/resources/META-INF/icon.png rename to slideshowfx-dropbox-hosting-connector/src/main/resources/META-INF/icon.png diff --git a/slideshowfx-engines/build.gradle b/slideshowfx-engines/build.gradle new file mode 100755 index 00000000..954c6c9d --- /dev/null +++ b/slideshowfx-engines/build.gradle @@ -0,0 +1,12 @@ +description = 'Module defining engines used by SlideshowFX' +version = '1.2' + +apply plugin: 'java-library' + +dependencies { + compile project(':slideshowfx-content-extension') + compile project(':slideshowfx-global-configuration') + compile project(':slideshowfx-utils') + + compile "org.freemarker:freemarker:${rootProject.ext.dependencies.freemarker.version}" +} \ No newline at end of file diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/AbstractEngine.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/AbstractEngine.java similarity index 98% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/AbstractEngine.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/AbstractEngine.java index 19046dc0..57b9af50 100755 --- a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/AbstractEngine.java +++ b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/AbstractEngine.java @@ -6,7 +6,10 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; /** * This class implements {@link IEngine} in order to define base treatments used by all engine defined in SlideshowFX. diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/EngineException.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/EngineException.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/EngineException.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/EngineException.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/IConfiguration.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/IConfiguration.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/IConfiguration.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/IConfiguration.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/IEngine.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/IEngine.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/IEngine.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/IEngine.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/InvalidPresentationConfigurationException.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/InvalidPresentationConfigurationException.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/InvalidPresentationConfigurationException.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/InvalidPresentationConfigurationException.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationEngine.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationEngine.java similarity index 96% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationEngine.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationEngine.java index 9acb304b..6ea11df3 100755 --- a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationEngine.java +++ b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationEngine.java @@ -14,6 +14,7 @@ import com.twasyl.slideshowfx.global.configuration.GlobalConfiguration; import com.twasyl.slideshowfx.utils.*; import com.twasyl.slideshowfx.utils.beans.Pair; +import com.twasyl.slideshowfx.utils.io.IOUtils; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; @@ -38,7 +39,7 @@ * The extension of a presentation is {@code sfx}. * * @author Thierry Wasylczenko - * @version 1.2 + * @version 1.3 * @since SlideshowFX 1.0 */ public class PresentationEngine extends AbstractEngine { @@ -49,11 +50,17 @@ public class PresentationEngine extends AbstractEngine { @@ -659,10 +675,10 @@ public void savePresentationFile() { private String buildJavaScriptResourcesToInclude() { final StringBuilder builder = new StringBuilder(); - builder.append(ResourceHelper.readResource(TEMPLATE_SFX_CONTENT_DEFINER_SCRIPT)).append("\n\n") - .append(ResourceHelper.readResource(TEMPLATE_SFX_SNIPPET_EXECUTOR_SCRIPT)).append("\n\n") - .append(ResourceHelper.readResource(TEMPLATE_SFX_CALLBACK_SCRIPT)).append("\n\n") - .append(ResourceHelper.readResource(TEMPLATE_SFX_QUIZ_CALLER_SCRIPT)).append("\n\n"); + builder.append(IOUtils.read(PresentationEngine.class.getResourceAsStream(TEMPLATE_SFX_CONTENT_DEFINER_SCRIPT))).append("\n\n") + .append(IOUtils.read(PresentationEngine.class.getResourceAsStream(TEMPLATE_SFX_SNIPPET_EXECUTOR_SCRIPT))).append("\n\n") + .append(IOUtils.read(PresentationEngine.class.getResourceAsStream(TEMPLATE_SFX_CALLBACK_SCRIPT))).append("\n\n") + .append(IOUtils.read(PresentationEngine.class.getResourceAsStream(TEMPLATE_SFX_QUIZ_CALLER_SCRIPT))).append("\n\n"); return builder.toString(); } diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationException.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationException.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationException.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/PresentationException.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/Presentations.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/Presentations.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/Presentations.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/Presentations.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/PresentationConfiguration.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/PresentationConfiguration.java similarity index 64% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/PresentationConfiguration.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/PresentationConfiguration.java index 641c83ba..e73ceabd 100755 --- a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/PresentationConfiguration.java +++ b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/PresentationConfiguration.java @@ -12,6 +12,10 @@ /** * Represents a presentation + * + * @author Thierry Wasylczenko + * @version 1.1 + * @since SlideshowFX 1.0 */ public class PresentationConfiguration implements IConfiguration { public static final String PRESENTATION = "presentation"; @@ -26,6 +30,7 @@ public class PresentationConfiguration implements IConfiguration { public static final String SLIDE_ID = "id"; public static final String SLIDE_NUMBER = "number"; public static final String SLIDE_TEMPLATE_ID = "template-id"; + public static final String SLIDE_SPEAKER_NOTES = "speaker-notes"; public static final String SLIDE_ELEMENTS = "elements"; public static final String SLIDE_ELEMENT_TEMPLATE_ID = "template-id"; public static final String SLIDE_ELEMENT_ELEMENT_ID = "element-id"; @@ -42,21 +47,46 @@ public class PresentationConfiguration implements IConfiguration { private Set> variables = new LinkedHashSet<>(); private List slides = new ArrayList<>(); - public long getId() { return id; } - public void setId(long id) { this.id = id; } + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public File getPresentationFile() { + return presentationFile; + } + + public void setPresentationFile(File presentationFile) { + this.presentationFile = presentationFile; + } + + public List getSlides() { + return slides; + } + + public void setSlides(List slides) { + this.slides = slides; + } - public File getPresentationFile() { return presentationFile; } - public void setPresentationFile(File presentationFile) { this.presentationFile = presentationFile; } + public Document getDocument() { + return document; + } - public List getSlides() { return slides; } - public void setSlides(List slides) { this.slides = slides; } + public void setDocument(Document document) { + this.document = document; + } - public Document getDocument() { return document; } - public void setDocument(Document document) { this.document = document; } + public Set getCustomResources() { + return customResources; + } - public Set getCustomResources() { return customResources; } + public Set> getVariables() { + return this.variables; + } - public Set> getVariables() { return this.variables; } public void setVariables(Collection> variables) { this.variables.clear(); this.variables.addAll(variables); @@ -64,12 +94,13 @@ public void setVariables(Collection> variables) { /** * Update the thumbnail of a given slide identified by its number. + * * @param slideNumber The number of the slide to update the thumbnail. - * @param image The new thumbnail. + * @param image The new thumbnail. * @throws IllegalArgumentException If the slide number is {@code null}. */ public void updateSlideThumbnail(String slideNumber, Image image) { - if(slideNumber == null) throw new IllegalArgumentException("The slide number can not be null"); + if (slideNumber == null) throw new IllegalArgumentException("The slide number can not be null"); for (Slide s : getSlides()) { if (slideNumber.equals(s.getSlideNumber())) { @@ -82,58 +113,56 @@ public void updateSlideThumbnail(String slideNumber, Image image) { /** * Get a slide by it's slide number. + * * @param slideNumber The slide number og the slide to get. - * @return The slide or null if not found. + * @return The slide or {@code null} if not found. */ public Slide getSlideByNumber(String slideNumber) { - Slide slide = null; - - Optional slideOpt = slides.stream() + final Slide slide = slides.stream() .filter(s -> slideNumber.equals(s.getSlideNumber())) - .findFirst(); - - if(slideOpt.isPresent()) slide = slideOpt.get(); + .findFirst() + .orElse(null); return slide; } /** * Get a slide by it's ID. + * * @param id The ID of the slide to get. - * @return The slide or null if not found. + * @return The slide or {@code null} if not found. */ public Slide getSlideById(String id) { - Slide slide = null; - - Optional slideOpt = slides.stream() - .filter(s -> id.equals(s.getId())) - .findFirst(); - - if(slideOpt.isPresent()) slide = slideOpt.get(); + final Slide slide = slides.stream() + .filter(s -> Objects.equals(id, s.getId())) + .findAny() + .orElse(null); return slide; } /** * Get the first slide of the presentation. If the presentation has no slides, {@code null} is returned. + * * @return The first slide of the presentation. */ public Slide getFirstSlide() { Slide slide = null; - if(!this.slides.isEmpty()) slide = this.slides.get(0); + if (!this.slides.isEmpty()) slide = this.slides.get(0); return slide; } /** * Get the last slide of the presentation. If the presentation has no slides, {@code null} is returned. + * * @return The last slide from the presentation. */ public Slide getLastSlide() { Slide slide = null; - if(!this.slides.isEmpty()) slide = this.slides.get(this.slides.size() - 1); + if (!this.slides.isEmpty()) slide = this.slides.get(this.slides.size() - 1); return slide; } @@ -141,19 +170,20 @@ public Slide getLastSlide() { /** * Get the slide before a given slide number. If the slide identified by the given slide number is not found, * {@code null} will be returned. + * * @param slideNumber The slide number of the slide to get the previous slide. * @return The slide before the given slide number. */ public Slide getSlideBefore(final String slideNumber) { Slide slide = null; - if(!this.slides.isEmpty()) { + if (!this.slides.isEmpty()) { int index = 0; - while(slide == null && index < this.slides.size()) { + while (slide == null && index < this.slides.size()) { final Slide currentSlide = this.slides.get(index); - if(currentSlide.getSlideNumber().equals(slideNumber)) { + if (currentSlide.getSlideNumber().equals(slideNumber)) { if (index != 0) slide = this.slides.get(index - 1); break; } @@ -164,6 +194,33 @@ public Slide getSlideBefore(final String slideNumber) { return slide; } + /** + * Get the slide after a given slide number. If the slide identified by the given slide number is not found, + * {@code null} will be returned. + * + * @param slideNumber The slide number of the slide to get the next slide. + * @return The slide after the given slide number. + */ + public Slide getSlideAfter(final String slideNumber) { + Slide slide = null; + + if (!this.slides.isEmpty()) { + int index = 0; + + while (slide == null && index < this.slides.size()) { + final Slide currentSlide = this.slides.get(index); + + if (currentSlide.getSlideNumber().equals(slideNumber)) { + if (index < this.slides.size() - 1) slide = this.slides.get(index + 1); + break; + } + index++; + } + } + + return slide; + } + /** * Update the given {@code slide} in the HTML file. Each {@link SlideElement} * of the {@code slide} in the HTML document is updated. @@ -172,14 +229,15 @@ public Slide getSlideBefore(final String slideNumber) { * will not be updated. * If the slide contains variables outside the {@link Slide#elements elements} * they will also be replaced in the HTML document. + * * @param slide The slide to update in the HTML document. */ public void updateSlideInDocument(final Slide slide) { - if(slide == null) throw new IllegalArgumentException("The slide can not be null"); + if (slide == null) throw new IllegalArgumentException("The slide can not be null"); slide.getElements() .stream() .forEach(element -> this.document.getElementById(element.getId()) - .html(element.getClearedHtmlContent(this.variables))); + .html(element.getClearedHtmlContent(this.variables))); } } diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/Slide.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/Slide.java old mode 100644 new mode 100755 similarity index 68% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/Slide.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/Slide.java index 37783cc4..6d5e000d --- a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/Slide.java +++ b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/Slide.java @@ -3,11 +3,14 @@ import com.twasyl.slideshowfx.engine.template.configuration.SlideTemplate; import javafx.scene.image.Image; +import java.util.Base64; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.getDefaultCharset; + /** * Represents a slide of the presentation. The slide is composed of: *
    @@ -16,11 +19,12 @@ *
  • a {@link #getSlideNumber() number} ;
  • *
  • a {@link #getThumbnail() thumbnail} which is like a screenshot of the slide in order to display it in * the SlideshowFX' UI ;
  • - *
  • a collection of {@link #getElements() elements} which correspond to all dynamic elements of the slide
  • + *
  • a collection of {@link #getElements() elements} which correspond to all dynamic elements of the slide ;
  • + *
  • speaker notes.
  • *
* * @author Thierry Wasylczenko - * @version 1.0.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class Slide { @@ -31,6 +35,7 @@ public class Slide { private String slideNumber; private Image thumbnail; private final Set elements = new HashSet<>(); + private String speakerNotes; public Slide() { } @@ -56,6 +61,49 @@ public Slide(SlideTemplate template, String slideNumber) { public Image getThumbnail() { return thumbnail; } public void setThumbnail(Image thumbnail) { this.thumbnail = thumbnail; } + public String getSpeakerNotes() { return speakerNotes; } + + /** + * Get the speaker notes of this {@link Slide} encoded in Base64. + * + * @return The speaker notes encoded in Base64. + */ + public String getSpeakerNotesAsBase64() { + final String base64 = Base64.getEncoder().encodeToString(getSpeakerNotes().getBytes(getDefaultCharset())); + return base64; + } + + /** + * Sets the speaker notes for this {@link Slide}. The speaker notes are only set if they are not + * {@code null} and not empty. + * + * @param speakerNotes The speaker notes to set. + */ + public void setSpeakerNotes(String speakerNotes) { + final String trimmedSpeakerNotes = speakerNotes != null ? speakerNotes.trim() : null; + this.speakerNotes = trimmedSpeakerNotes; + } + + /** + * Set the speaker notes of this {@link Slide}. The speaker notes are decoded from Base64. + * + * @param speakerNotesAsBase64 The speaker notes encoded in Base64. + */ + public void setSpeakerNotesAsBase64(final String speakerNotesAsBase64) { + final byte[] bytes = Base64.getDecoder().decode(speakerNotesAsBase64); + setSpeakerNotes(new String(bytes, getDefaultCharset())); + } + + /** + * Indicates of the current {@link Slide} has speaker notes defined. The slide is considered having + * speaker notes if {@link #getSpeakerNotes()} returns a non {@code null} and non empty string. + * + * @return {@code true} if the {@link Slide} has speaker notes, {@code false} otherwise. + */ + public boolean hasSpeakerNotes() { + return this.getSpeakerNotes() != null && !this.getSpeakerNotes().isEmpty(); + } + /** * The elements contained in the slide. * @return The collection containing the slide elements. diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/SlideElement.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/SlideElement.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/SlideElement.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/presentation/configuration/SlideElement.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/DynamicAttribute.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/DynamicAttribute.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/DynamicAttribute.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/DynamicAttribute.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateConfigurationException.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateConfigurationException.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateConfigurationException.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateConfigurationException.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateException.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateException.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateException.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/InvalidTemplateException.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/TemplateEngine.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/TemplateEngine.java similarity index 97% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/TemplateEngine.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/TemplateEngine.java index 0d80ea8b..1676d836 100755 --- a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/TemplateEngine.java +++ b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/TemplateEngine.java @@ -26,7 +26,7 @@ * The extension of a template is {@code sfxt}. * * @author Thierry Wasylczenko - * @version 1.1 + * @version 1.2 * @since SlideshowFX 1.0 */ public class TemplateEngine extends AbstractEngine { @@ -36,13 +36,19 @@ public class TemplateEngine extends AbstractEngine { * The default extension for template archives. Value is {@value #DEFAULT_ARCHIVE_EXTENSION}. */ public static final String DEFAULT_ARCHIVE_EXTENSION = "sfxt"; + + /** + * The default file name of the configuration file. + */ + public static final String DEFAULT_CONFIGURATION_FILE_NAME = "template-config.json"; + /** * The default value, containing the dot, for presentation archives. */ public static final String DEFAULT_DOTTED_ARCHIVE_EXTENSION = ".".concat(DEFAULT_ARCHIVE_EXTENSION); public TemplateEngine() { - super(DEFAULT_ARCHIVE_EXTENSION, "template-config.json"); + super(DEFAULT_ARCHIVE_EXTENSION, DEFAULT_CONFIGURATION_FILE_NAME); } @Override @@ -62,6 +68,9 @@ public TemplateConfiguration readConfiguration(Reader reader) throws NullPointer templateConfiguration.setName(templateJson.getString(TEMPLATE_NAME)); LOGGER.fine("[Template configuration] name = " + templateConfiguration.getName()); + templateConfiguration.setVersion(templateJson.getString(TEMPLATE_VERSION, null)); + LOGGER.fine("[Template configuration] version = " + templateConfiguration.getVersion()); + templateConfiguration.setFile(new File(getWorkingDirectory(), templateJson.getString(TEMPLATE_FILE))); LOGGER.fine("[Template configuration] file = " + templateConfiguration.getFile().getAbsolutePath()); diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideElementTemplate.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideElementTemplate.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideElementTemplate.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideElementTemplate.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideTemplate.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideTemplate.java similarity index 100% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideTemplate.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/SlideTemplate.java diff --git a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/TemplateConfiguration.java b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/TemplateConfiguration.java similarity index 96% rename from SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/TemplateConfiguration.java rename to slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/TemplateConfiguration.java index c91142ee..f76d39a7 100755 --- a/SlideshowFX-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/TemplateConfiguration.java +++ b/slideshowfx-engines/src/main/java/com/twasyl/slideshowfx/engine/template/configuration/TemplateConfiguration.java @@ -12,12 +12,13 @@ * Represents the template found in the template configuration file. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class TemplateConfiguration implements IConfiguration { public static final String TEMPLATE = "template"; public static final String TEMPLATE_NAME = "name"; + public static final String TEMPLATE_VERSION = "version"; public static final String TEMPLATE_FILE = "file"; public static final String JS_OBJECT = "js-object"; public static final String TEMPLATE_RESOURCES_DIRECTORY = "resources-directory"; @@ -48,6 +49,7 @@ public class TemplateConfiguration implements IConfiguration { private static final Logger LOGGER = Logger.getLogger(TemplateConfiguration.class.getName()); private String name; + private String version; private File file; private Set> defaultVariables; private List slideTemplates; @@ -70,6 +72,9 @@ public TemplateConfiguration() { public String getName() { return name; } public void setName(String name) { this.name = name; } + public String getVersion() { return version; } + public void setVersion(String version) { this.version = version;} + public File getFile() { return file; } public void setFile(File file) { this.file = file; } diff --git a/slideshowfx-global-configuration/build.gradle b/slideshowfx-global-configuration/build.gradle new file mode 100644 index 00000000..114a7c65 --- /dev/null +++ b/slideshowfx-global-configuration/build.gradle @@ -0,0 +1,10 @@ +description = 'Module defining the global configuration of SlideshowFX' +version = '1.1' + +apply plugin: 'java' + +dependencies { + compile project(':slideshowfx-logs') + + testCompile "org.mockito:mockito-core:${rootProject.ext.dependencies.mockito.version}" +} \ No newline at end of file diff --git a/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/ContextFileException.java b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/ContextFileException.java new file mode 100755 index 00000000..214d4c48 --- /dev/null +++ b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/ContextFileException.java @@ -0,0 +1,29 @@ +package com.twasyl.slideshowfx.global.configuration; + +/** + * Exception class when working with the SlideshowFX context file. + * + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 2.0 + */ +public class ContextFileException extends Exception { + public ContextFileException() { + } + + public ContextFileException(String message) { + super(message); + } + + public ContextFileException(String message, Throwable cause) { + super(message, cause); + } + + public ContextFileException(Throwable cause) { + super(cause); + } + + public ContextFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/ContextFileWorker.java b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/ContextFileWorker.java new file mode 100755 index 00000000..2df2d6ad --- /dev/null +++ b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/ContextFileWorker.java @@ -0,0 +1,571 @@ +package com.twasyl.slideshowfx.global.configuration; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.*; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.getDefaultCharset; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.xml.xpath.XPathConstants.NODE; + +/** + * Class for interacting with a SlideshowFX context file. + * + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 2.0 + */ +public class ContextFileWorker { + private static final Logger LOGGER = Logger.getLogger(ContextFileWorker.class.getName()); + private static DocumentBuilder DOCUMENT_BUILDER; + private static XPath XPATH; + + static { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + DOCUMENT_BUILDER = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + LOGGER.log(Level.SEVERE, "Can not create a document builder instance", e); + } + + final XPathFactory xPathFactory = XPathFactory.newInstance(); + XPATH = xPathFactory.newXPath(); + } + + protected static final String ROOT_TAG = "slideshowfx"; + protected static final String RECENT_PRESENTATIONS_TAG = "recentPresentations"; + protected static final String RECENT_PRESENTATION_TAG = "recentPresentation"; + protected static final String ID_TAG = "id"; + protected static final String FILE_TAG = "file"; + protected static final String OPENED_DATE_TIME_TAG = "openedDateTime"; + + /** + * Determine from the given {@code contextFile} the recent presentations that have been opened by SlideshowFX. + * + * @param contextFile The context file to read. + * @return A never {@code null} collection of the recent presentations opened by SlideshowFX. + * @throws ContextFileException If the given {@code contextFile} is {@code null}. + */ + public static Set readRecentPresentationFromFile(final File contextFile) throws ContextFileException { + if (contextFile == null) throw new NullPointerException("The context file can not be null"); + + final InputStream input; + try { + input = getInputStreamForFile(contextFile); + } catch (IOException e) { + throw new ContextFileException(e); + } + + return readRecentPresentationFromStream(input); + } + + /** + * Save a given {@code recentPresentation} to a specified {@code contextFile}. If the context file doesn't exist or + * is empty, it's structure will be created in order to properly add the recent presentation to the file. + * + * @param contextFile The context file to write to. + * @param recentPresentation The presentation to add. + * @throws ContextFileException + */ + public static void saveRecentPresentationToFile(final File contextFile, final RecentPresentation recentPresentation) throws ContextFileException { + if (contextFile == null) throw new NullPointerException("The context file can not be null"); + + final InputStream input; + final OutputStream output; + try { + input = getInputStreamForFile(contextFile); + output = new FileOutputStream(contextFile); + } catch (IOException e) { + throw new ContextFileException(e); + } + + saveRecentPresentation(input, output, recentPresentation); + } + + /** + * Update a given {@code recentPresentation} to a specified {@code contextFile}. If the context file doesn't exist or + * is empty, it's structure will be created in order to properly add the recent presentation to the file. + * + * @param contextFile The context file to write to. + * @param recentPresentation The presentation to add. + * @throws ContextFileException + */ + public static void updateRecentPresentationInFile(final File contextFile, final RecentPresentation recentPresentation) throws ContextFileException { + if (contextFile == null) throw new NullPointerException("The context file can not be null"); + + final InputStream input; + final OutputStream output; + try { + input = getInputStreamForFile(contextFile); + output = new FileOutputStream(contextFile); + } catch (IOException e) { + throw new ContextFileException(e); + } + + updateRecentPresentation(input, output, recentPresentation); + } + + /** + * Tests if a given {@link RecentPresentation recentPresentation} is found by it's ID in the given document. + * + * @param contextFile The document to search in. + * @param recentPresentation The presentation to search. + * @return {@code true} if the presentation has been found in the document, {@code false} otherwise. + */ + public static boolean recentPresentationAlreadyPresent(final File contextFile, final RecentPresentation recentPresentation) throws ContextFileException { + if (contextFile == null) throw new NullPointerException("The context file can not be null"); + + final InputStream input; + try { + input = getInputStreamForFile(contextFile); + } catch (IOException e) { + throw new ContextFileException(e); + } + + return recentPresentationAlreadyPresent(input, recentPresentation); + } + + /** + * Purge the oldest recent presentations from the given document represented by {@code contextFile} in order to keep + * at most the given number of recent presentations. + * + * @param contextFile The document to purge from. + * @param numberOfRecentPresentationsToKeep The number of recent presentations to keep. + * @return The recent presentations that are now stored in the context file. + * @throws ContextFileException + */ + public static Set purgeRecentPresentations(final File contextFile, final long numberOfRecentPresentationsToKeep) throws ContextFileException { + if (contextFile == null) throw new NullPointerException("The context file can not be null"); + if (numberOfRecentPresentationsToKeep < 0) + throw new IllegalArgumentException("The number of recent presentations to keep can not be negative"); + + final InputStream input; + final OutputStream output; + try { + input = getInputStreamForFile(contextFile); + output = new FileOutputStream(contextFile); + } catch (IOException e) { + throw new ContextFileException(e); + } + + return purgeRecentPresentations(input, output, numberOfRecentPresentationsToKeep); + } + + /** + *

Save a given {@code recentPresentation} to a given {@code output}. The method will consider the {@code input} + * object as the XML document and will try to read it and the result of the appending will be written in the {@code output}.

+ *

+ * If the input is considered empty (meaning {@link InputStream#available()} returns {@code 0}), then the structure + * of XML file will be created in order to properly add the presentation.

+ * + * @param input The document to which append the presentation. + * @param output The document where the result of the appending will be persisted. + * @param recentPresentation The presentation to add. + * @throws ContextFileException + */ + protected static void saveRecentPresentation(final InputStream input, final OutputStream output, + final RecentPresentation recentPresentation) throws ContextFileException { + final Document document = createDocumentFromInput(input); + + populateDocumentIfNecessary(document); + + final Node recentPresentationsNode = getRecentPresentationsNode(document); + final Node recentPresentationNode = createNodeFromRecentPresentation(document, recentPresentation); + + if (recentPresentationNode != null) { + recentPresentationsNode.appendChild(recentPresentationNode); + + writeDocument(document, output); + } + } + + /** + *

Update a given {@code recentPresentation} to a given {@code output}. The method will consider the {@code input} + * object as the XML document and will try to read it and the result of the update will be written in the {@code output}.

+ *

If the input is considered empty (meaning {@link InputStream#available()} returns {@code 0}), then the structure + * of XML file will be created in order to properly add the presentation.

+ * + * @param input The document to which update the presentation. + * @param output The document where the result of the update will be persisted. + * @param recentPresentation The presentation to update. + * @throws ContextFileException + */ + protected static void updateRecentPresentation(final InputStream input, final OutputStream output, + final RecentPresentation recentPresentation) throws ContextFileException { + final Document document = createDocumentFromInput(input); + populateDocumentIfNecessary(document); + + Node recentPresentationNode = null; + + try { + recentPresentationNode = findRecentPresentationNodeFromID(document, recentPresentation); + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "Error when trying to find the recent presentation in document", e); + } + + final Node recentPresentationsNode = getRecentPresentationsNode(document); + final Node newRecentPresentationNode = createNodeFromRecentPresentation(document, recentPresentation); + + if (recentPresentationNode != null) { + recentPresentationsNode.removeChild(recentPresentationNode); + } + + recentPresentationsNode.appendChild(newRecentPresentationNode); + + writeDocument(document, output); + } + + /** + * Tests if a given {@link RecentPresentation recentPresentation} is found by it's ID in the given document. + * + * @param input The document to search in. + * @param recentPresentation The presentation to search. + * @return {@code true} if the presentation has been found in the document, {@code false} otherwise. + */ + protected static boolean recentPresentationAlreadyPresent(final InputStream input, final RecentPresentation recentPresentation) { + boolean presentationFound = false; + + try { + final Document document = DOCUMENT_BUILDER.parse(input); + + presentationFound = findRecentPresentationNodeFromID(document, recentPresentation) != null; + } catch (ContextFileException | SAXException | IOException e) { + LOGGER.log(Level.FINE, "Can not read XML document", e); + } + + return presentationFound; + } + + /** + * Read all recent presentations from the provided document. + * + * @param input The document to read the recent presentations from. + * @return A never {@code null} collection of the recent presentations stored within the document. + */ + protected static Set readRecentPresentationFromStream(final InputStream input) { + final Set presentations = new TreeSet<>(); + + try { + final Document document = createDocumentFromInput(input); + + final Node recentPresentationsNode = getRecentPresentationsNode(document); + + if (recentPresentationsNode != null && recentPresentationsNode.hasChildNodes()) { + final List recentPresentationNodes = getRecentPresentationNodes(recentPresentationsNode); + + presentations.addAll( + recentPresentationNodes.stream() + .map(ContextFileWorker::buildRecentPresentationFromNode) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } + } catch (ContextFileException e) { + LOGGER.log(Level.SEVERE, "The context file can not be read", e); + } + + return presentations; + } + + /** + * Purge the oldest recent presentations from the given document represented by {@code input} in order to keep at most + * the given number of recent presentations. + * + * @param input The document to purge from. + * @param output The document to write to. + * @param numberOfRecentPresentationsToKeep The number of recent presentations to keep. + * @return The recent presentations that are now stored in the document. + * @throws ContextFileException + */ + public static Set purgeRecentPresentations(final InputStream input, final OutputStream output, final long numberOfRecentPresentationsToKeep) throws ContextFileException { + Set presentations = readRecentPresentationFromStream(input); + + if (presentations.size() > numberOfRecentPresentationsToKeep) { + final Comparator byDateDesc = (rp1, rp2) -> { + int result; + + if (rp1.getOpenedDateTime() == null && rp2.getNormalizedPath() != null) { + result = -1; + } else if (rp1.getOpenedDateTime() != null && rp2.getOpenedDateTime() == null) { + result = 1; + } else { + result = -rp1.getOpenedDateTime().compareTo(rp2.getOpenedDateTime()); + } + + return result; + }; + + Set presentationsSortedByDateDesc = new TreeSet<>(byDateDesc); + presentationsSortedByDateDesc.addAll(presentations); + + presentationsSortedByDateDesc = presentationsSortedByDateDesc.stream() + .limit(numberOfRecentPresentationsToKeep) + .collect(Collectors.toSet()); + + final Document document = createDocumentFromInput(null); + final Node recentPresentationsNode = getRecentPresentationsNode(document); + + presentationsSortedByDateDesc.stream() + .map(presentation -> createNodeFromRecentPresentation(document, presentation)) + .forEach(recentPresentationsNode::appendChild); + + writeDocument(document, output); + + presentations = new TreeSet<>(presentationsSortedByDateDesc); + } + + return presentations; + } + + /** + * Get the node that contains all recent presentations, which is named {@value #RECENT_PRESENTATIONS_TAG}. + * + * @param document The document from which read the recent presentations. + * @return The node containing all recent presentations or {@code null} if not found. + */ + protected static Node getRecentPresentationsNode(final Document document) { + Node recentPresentationsNode = null; + + final NodeList list = document.getElementsByTagName(RECENT_PRESENTATIONS_TAG); + if (list != null && list.getLength() > 0) { + recentPresentationsNode = list.item(0); + } + + return recentPresentationsNode; + } + + /** + * Get all nodes named {@value #RECENT_PRESENTATION_TAG} within the node containing all presentations. + * + * @param recentPresentationsNode The node containing all recent presentations. + * @return A never {@code null} collection of all recent presentation nodes. + */ + protected static List getRecentPresentationNodes(final Node recentPresentationsNode) { + final List recentPresentations = new ArrayList<>(); + + final NodeList children = recentPresentationsNode.getChildNodes(); + if (children != null) { + for (int index = 0; index < children.getLength(); index++) { + final Node item = children.item(index); + + if (RECENT_PRESENTATION_TAG.equals(item.getNodeName())) { + recentPresentations.add(item); + } + } + } + + return recentPresentations; + } + + /** + * Build a {@link RecentPresentation} object from the given {@link Node node}. In order to be created, the node + * must contain a {@value #FILE_TAG} tag as well as a {@link #OPENED_DATE_TIME_TAG} tag. Otherwise, a {@code null} + * object will be returned. + * + * @param node The node that will be used to create a {@link RecentPresentation}. + * @return A {@link RecentPresentation} instance created from the given node. + */ + protected static RecentPresentation buildRecentPresentationFromNode(final Node node) { + RecentPresentation recentPresentation = null; + LocalDateTime openedDateTime = null; + String path = null; + + final NodeList children = node.getChildNodes(); + int index = 0; + + while (index < children.getLength() && (openedDateTime == null || path == null)) { + final Node child = children.item(index++); + + switch (child.getNodeName()) { + case OPENED_DATE_TIME_TAG: + openedDateTime = LocalDateTime.parse(child.getTextContent()); + break; + case FILE_TAG: + path = child.getTextContent(); + break; + default: + LOGGER.fine(child.getNodeName() + " not supported"); + } + } + + if (openedDateTime != null && path != null) { + recentPresentation = new RecentPresentation(path, openedDateTime); + } + + return recentPresentation; + } + + /** + * Populate a given {@link Document} instance in order to add :
+ *
    + *
  • the {@value #ROOT_TAG} root tag if needed;
  • + *
  • the {@value #RECENT_PRESENTATIONS_TAG} if needed.
  • + *
+ *

+ * If the document already respects this structure, nothing weel be added to it. + * + * @param document The document to populate. + */ + protected static void populateDocumentIfNecessary(final Document document) { + Element root = document.getDocumentElement(); + if (root == null) { + root = document.createElement(ROOT_TAG); + document.appendChild(root); + } + + try { + final StringBuilder expression = new StringBuilder("/").append(ROOT_TAG) + .append("/").append(RECENT_PRESENTATIONS_TAG).append("[1]"); + Node recentPresentationsNode = (Node) XPATH.evaluate(expression.toString(), root, NODE); + + if (recentPresentationsNode == null) { + recentPresentationsNode = document.createElement(RECENT_PRESENTATIONS_TAG); + root.appendChild(recentPresentationsNode); + } + } catch (XPathExpressionException e) { + LOGGER.log(Level.WARNING, "Can not parse the document properly", e); + } + } + + /** + * Create a {@link Node} instance from the given {@link RecentPresentation recentPresentation}. If the presentation + * doesn't have a path or a {@link RecentPresentation#getOpenedDateTime()}, {@code null} will be returned. + * + * @param document The document that will be used to create a {@link Node node} instance. + * @param recentPresentation The presentation to create the node for. + * @return A well created {@link Node node} with information from the presentation. + */ + protected static Node createNodeFromRecentPresentation(final Document document, final RecentPresentation recentPresentation) { + if (recentPresentation == null || recentPresentation.getOpenedDateTime() == null + || recentPresentation.getAbsolutePath() == null || recentPresentation.getAbsolutePath().trim().isEmpty()) { + return null; + } + + final Element recentPresentationNode = document.createElement(RECENT_PRESENTATION_TAG); + final Element idNode = document.createElement(ID_TAG); + final Element fileNode = document.createElement(FILE_TAG); + final Element openedDateTimeNode = document.createElement(OPENED_DATE_TIME_TAG); + + idNode.setTextContent(recentPresentation.getId()); + fileNode.setTextContent(recentPresentation.getNormalizedPath()); + openedDateTimeNode.setTextContent(recentPresentation.getOpenedDateTime().toString()); + + recentPresentationNode.appendChild(idNode); + recentPresentationNode.appendChild(fileNode); + recentPresentationNode.appendChild(openedDateTimeNode); + + return recentPresentationNode; + } + + /** + * Search a given {@link RecentPresentation recent presentation} by it's ID within the document. If the document + * contains the presentation, it will be returned, otherwise {@code null} is returned. + * + * @param document The document to search in. + * @param recentPresentation The presentation to search by it's ID. + * @return The node corresponding to the given presentation or {@code null} if not found. + */ + protected static Node findRecentPresentationNodeFromID(final Document document, final RecentPresentation recentPresentation) throws ContextFileException { + final StringBuilder expression = new StringBuilder("/").append(ROOT_TAG) + .append("/").append(RECENT_PRESENTATIONS_TAG) + .append("/").append(RECENT_PRESENTATION_TAG) + .append("[").append(ID_TAG).append(" = '").append(recentPresentation.getId()) + .append("'][1]"); + + try { + return (Node) XPATH.evaluate(expression.toString(), document.getDocumentElement(), NODE); + } catch (XPathExpressionException e) { + throw new ContextFileException(e); + } + } + + /** + * Write a given {@link Document document} to a given {@link OutputStream output}. + * + * @param document The document to write. + * @param output The output where to write the document. + */ + protected static void writeDocument(Document document, OutputStream output) { + final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + try { + final Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, UTF_8.displayName()); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + final DOMSource source = new DOMSource(document); + final StreamResult result = new StreamResult(output); + transformer.transform(source, result); + } catch (TransformerException e) { + LOGGER.log(Level.SEVERE, "Can not write to the document", e); + } + } + + /** + * Create an {@link InputStream} with the default body of the XML document. + * + * @return An {@link InputStream} with the default body of the XML document. + */ + protected static InputStream createDefaultDocumentBodyInputStream() { + final StringBuilder body = new StringBuilder("<").append(ROOT_TAG).append(">") + .append("<").append(RECENT_PRESENTATIONS_TAG).append(">") + .append(""); + + return new ByteArrayInputStream(body.toString().getBytes(getDefaultCharset())); + } + + /** + * Get an {@link InputStream} for a given context file. If the context file doesn't exist, then an {@link InputStream} + * with a default XML body will be returned. + * + * @param contextFile The file for which create an {@link InputStream}. + * @return An {@link InputStream} for the given context file. + * @throws IOException If an error occurs when reading the context file. + */ + protected static InputStream getInputStreamForFile(File contextFile) throws IOException { + return contextFile.exists() ? new ByteArrayInputStream(Files.readAllBytes(contextFile.toPath())) : createDefaultDocumentBodyInputStream(); + } + + /** + * Creates an instance of {@link Document} from the given {@link InputStream input}. If {@code input.available() == 0} + * or if the given input is {@code null} then the result of the method {@link #createDefaultDocumentBodyInputStream()} + * is used for creating the document. + * + * @param input The input to create the document from. + * @return An instance of {@link Document}. + * @throws ContextFileException + */ + protected static Document createDocumentFromInput(InputStream input) throws ContextFileException { + Document document; + try { + if (input == null || input.available() == 0) { + document = DOCUMENT_BUILDER.parse(createDefaultDocumentBodyInputStream()); + } else { + document = DOCUMENT_BUILDER.parse(input); + } + } catch (IOException | SAXException e) { + throw new ContextFileException(e); + } + + return document; + } +} diff --git a/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/GlobalConfiguration.java b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/GlobalConfiguration.java new file mode 100755 index 00000000..5b4ae663 --- /dev/null +++ b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/GlobalConfiguration.java @@ -0,0 +1,926 @@ +package com.twasyl.slideshowfx.global.configuration; + +import com.twasyl.slideshowfx.logs.SlideshowFXHandler; + +import java.io.*; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Properties; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.TimeUnit; +import java.util.logging.*; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * This class provides methods for accessing configuration properties. + * + * @author Thierry Wasylczenko + * @version 1.1 + * @since SlideshowFX 1.0 + */ +public class GlobalConfiguration { + private static final Logger LOGGER = Logger.getLogger(GlobalConfiguration.class.getName()); + + protected static final String APPLICATION_DIRECTORY_PROPERTY = "application.dir"; + protected static final String PLUGINS_DIRECTORY_PROPERTY = "plugins.dir"; + protected static final String TEMPLATE_LIBRARY_DIRECTORY_PROPERTY = "templateLib.dir"; + + protected static final String DEFAULT_APPLICATION_DIRECTORY_NAME = ".SlideshowFX"; + protected static final String DEFAULT_PLUGINS_DIRECTORY_NAME = "plugins"; + protected static final String DEFAULT_TEMPLATE_LIBRARY_DIRECTORY_NAME = "templateLibrary"; + protected static final String SLIDESHOWFX_CONFIGURATION_FILE = ".slideshowfx.configuration.properties"; + protected static final String SLIDESHOWFX_CONTEXT_FILE_NAME = ".slideshowfx.context.xml"; + protected static final Long DEFAULT_MAX_RECENT_PRESENTATIONS = 10L; + + private static File APPLICATION_DIRECTORY = null; + private static File PLUGINS_DIRECTORY = null; + private static File TEMPLATE_LIBRARY_DIRECTORY = null; + private static File CONFIG_FILE = null; + private static File LOGGING_CONFIG_FILE = null; + private static Set RECENT_PRESENTATIONS = null; + + /** + * Name of the parameter used to specify if auto saving files is enabled. The value of the parameter is a boolean. + */ + protected static final String AUTO_SAVING_ENABLED_PARAMETER = "application.autoSaving.enabled"; + + /** + * Name of the parameter used to specify the interval for auto saving files. The value of this parameter must be + * given in seconds. + */ + protected static final String AUTO_SAVING_INTERVAL_PARAMETER = "application.autoSaving.interval"; + + /** + * Name of the parameter used to specify whether the temporary files are deleted when the application is exiting. + * The value of the parameter is a boolean. + */ + protected static final String TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER = "application.temporaryFiles.deleteOnExit"; + + /** + * Name of the parameter used to specify how old can temporary files be before being deleted when exiting the + * application. The value of this parameter must be given in seconds. + */ + protected static final String TEMPORARY_FILES_MAX_AGE_PARAMETER = "application.temporaryFiles.maxAge"; + + /** + * Name of the parameter used to specify how many open presentations are stored and displayed in the "Open recent" + * menu of the application. + */ + protected static final String MAX_RECENT_PRESENTATIONS = "application.max.recentpresentations"; + + /** + * Name of the parameter used to specify the Twitter consumer key. + */ + protected static final String TWITTER_CONSUMER_KEY = "service.twitter.consumerKey"; + + /** + * Name of the parameter used to specify the Twitter consumer secret. + */ + protected static final String TWITTER_CONSUMER_SECRET = "service.twitter.consumerSecret"; + + /** + * The default {@link Charset} used by the application when writing files, readings files and converting strings. + */ + protected static final Charset DEFAULT_CHARSET = UTF_8; + + + /** + * Name of the parameter for defining the log level. + */ + protected static final String LOG_LEVEL_PARAMETER = ".level"; + + /** + * Name of the parameter for specifying the log handler. + */ + protected static final String LOG_HANDLERS_PARAMETER = "handlers"; + + /** + * Name of the parameter suffix for the specifying the encoding of the log file. + */ + protected static final String LOG_ENCODING_SUFFIX = ".encoding"; + + /** + * Name of the parameter for specifying the file log limit. + */ + protected static final String LOG_FILE_LIMIT_PARAMETER = "java.util.logging.FileHandler.limit"; + + /** + * Name of the parameter for specifying the pattern of the log file name. + */ + protected static final String LOG_FILE_PATTERN_PARAMETER = "java.util.logging.FileHandler.pattern"; + + /** + * Name of the parameter suffix for the log file formatter. + */ + protected static final String LOG_FORMATTER_SUFFIX = ".formatter"; + + /** + * Name of the parameter for specifying if logs must be appended to the log file. + */ + protected static final String LOG_FILE_APPEND_PARAMETER = "java.util.logging.FileHandler.append"; + + /** + * Get the application directory used to store the plugins and the configuration. The method will determine the + * directory by checking if there is a system property named {@value #APPLICATION_DIRECTORY_PROPERTY} that defines + * the directory to use and if not, the directory will be the {@value #DEFAULT_APPLICATION_DIRECTORY_NAME} directory + * stored in the user's home. + * + * @return The application directory. + */ + public synchronized static File getApplicationDirectory() { + if (APPLICATION_DIRECTORY == null) { + final Properties properties = System.getProperties(); + + if (properties.containsKey(APPLICATION_DIRECTORY_PROPERTY)) { + APPLICATION_DIRECTORY = new File(properties.getProperty(APPLICATION_DIRECTORY_PROPERTY)); + } else { + APPLICATION_DIRECTORY = new File(System.getProperty("user.home"), DEFAULT_APPLICATION_DIRECTORY_NAME); + } + } + + return APPLICATION_DIRECTORY; + } + + /** + * Get the plugins directory used to store the installed plugins. The method will determine the + * directory by checking if there is a system property named {@value #PLUGINS_DIRECTORY_PROPERTY} that defines + * the directory to use and if not, the directory will be the {@value #DEFAULT_PLUGINS_DIRECTORY_NAME} directory + * stored in the application directory returned by {@link #getApplicationDirectory()}. + * + * @return The plugins directory. + */ + public synchronized static File getPluginsDirectory() { + if (PLUGINS_DIRECTORY == null) { + final Properties properties = System.getProperties(); + + if (properties.containsKey(PLUGINS_DIRECTORY_PROPERTY)) { + PLUGINS_DIRECTORY = new File(properties.getProperty(PLUGINS_DIRECTORY_PROPERTY)); + } else { + PLUGINS_DIRECTORY = new File(getApplicationDirectory(), DEFAULT_PLUGINS_DIRECTORY_NAME); + } + } + return PLUGINS_DIRECTORY; + } + + /** + * Get the directory used to store the template's library. The method will determine the + * directory by checking if there is a system property named {@value #TEMPLATE_LIBRARY_DIRECTORY_PROPERTY} that + * defines the directory to use and if not, the directory will be the {@value #DEFAULT_TEMPLATE_LIBRARY_DIRECTORY_NAME} + * directory stored in the application directory returned by {@link #getApplicationDirectory()}. + * + * @return The template's library directory. + */ + public synchronized static File getTemplateLibraryDirectory() { + if (TEMPLATE_LIBRARY_DIRECTORY == null) { + final Properties properties = System.getProperties(); + + if (properties.containsKey(TEMPLATE_LIBRARY_DIRECTORY_PROPERTY)) { + TEMPLATE_LIBRARY_DIRECTORY = new File(properties.getProperty(TEMPLATE_LIBRARY_DIRECTORY_PROPERTY)); + } else { + TEMPLATE_LIBRARY_DIRECTORY = new File(getApplicationDirectory(), DEFAULT_TEMPLATE_LIBRARY_DIRECTORY_NAME); + } + } + return TEMPLATE_LIBRARY_DIRECTORY; + } + + /** + * Get the configuration file of the application. The file is named {@code .slideshowfx.configuration.properties} + * and is stored in the directory returned by {@link #getApplicationDirectory()}. + * + * @return The configuration file. + */ + public synchronized static File getConfigurationFile() { + if (CONFIG_FILE == null) { + CONFIG_FILE = new File(getApplicationDirectory(), SLIDESHOWFX_CONFIGURATION_FILE); + } + + return CONFIG_FILE; + } + + /** + * Get the logging configuration file. The method will determine the file by checking if there is a system property + * named {@code java.util.logging.config.file} that defines the file to use and if not, the file will be the + * {@code logging.config} file stored in the application directory returned by {@link #getApplicationDirectory()}. + * + * @return The logging configuration file. + */ + public synchronized static File getLoggingConfigFile() { + if (LOGGING_CONFIG_FILE == null) { + final Properties properties = System.getProperties(); + + if (properties.containsKey("java.util.logging.config.file")) { + LOGGING_CONFIG_FILE = new File(System.getProperty("java.util.logging.config.file")); + } else { + LOGGING_CONFIG_FILE = new File(getApplicationDirectory(), "logging.config"); + } + } + + return LOGGING_CONFIG_FILE; + } + + /** + * Creates the configuration directory represented by the {@link #APPLICATION_DIRECTORY} variable if it doesn't + * already exist. + * + * @return {@code true} if the application directory has been created by this method, {@code false} otherwise. + */ + public synchronized static boolean createApplicationDirectory() { + boolean created = false; + + if (!getApplicationDirectory().exists()) { + created = getApplicationDirectory().mkdirs(); + } + + return created; + } + + /** + * Creates the plugins directory represented by the {@link #PLUGINS_DIRECTORY} variable if it doesn't + * already exist. + * If parents directories don't exist, this method will not create them and the directory will not be created. + * + * @return {@code true} if the plugins directory has been created by this method, {@code false} otherwise. + */ + public synchronized static boolean createPluginsDirectory() { + boolean created = false; + + if (!getPluginsDirectory().exists()) { + created = getPluginsDirectory().mkdir(); + } + + return created; + } + + /** + * Creates the template library directory represented by the {@link #TEMPLATE_LIBRARY_DIRECTORY} variable if it doesn't + * already exist. + * If parents directories don't exist, this method will not create them and the directory will not be created. + * + * @return {@code true} if the template library directory has been created by this method, {@code false} otherwise. + */ + public synchronized static boolean createTemplateLibraryDirectory() { + boolean created = false; + + if (!getTemplateLibraryDirectory().exists()) { + created = getTemplateLibraryDirectory().mkdir(); + } + + return created; + } + + /** + * Creates the configuration file of the application, represented by the {@link #getConfigurationFile()} + * variable if it doesn't already exist. + * + * @return {@code true} if the configuration file has been created by this method, {@code false} otherwise. + */ + public synchronized static boolean createConfigurationFile() { + boolean created = false; + + if (!getConfigurationFile().exists()) { + try { + created = getConfigurationFile().createNewFile(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can not create the configuration file", e); + } + } + + return created; + } + + /** + * Creates the logging configuration file of the application, represented by the {@link #getLoggingConfigFile()} + * variable if it doesn't already exist. + * + * @return {@code true} if the logging configuration file has been created by this method, {@code false} otherwise. + */ + public synchronized static boolean createLoggingConfigurationFile() { + boolean created = false; + + if (!getLoggingConfigFile().exists()) { + try { + created = getLoggingConfigFile().createNewFile(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can not create the logging configuration file", e); + } + } + + return created; + } + + /** + * Creates the logging configuration file of the application, represented by the given {@code loggingConfigFile}. + * Calling this method will cause the {@link #getLoggingConfigFile()} method to return the provided file. + * + * @return {@code true} if the logging configuration file has been created by this method, {@code false} otherwise. + */ + public synchronized static boolean createLoggingConfigurationFile(final File loggingConfigFile) { + boolean created = false; + + if (!loggingConfigFile.exists()) { + try { + created = loggingConfigFile.createNewFile(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can not create the logging configuration file", e); + } + } + + LOGGING_CONFIG_FILE = loggingConfigFile; + + return created; + } + + /** + * Fill the configuration file with default values if it exists. + */ + public synchronized static void fillConfigurationWithDefaultValue() { + if (getConfigurationFile().exists()) { + final Properties properties = readAllPropertiesFromConfigurationFile(getConfigurationFile()); + + if (!properties.containsKey(TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER)) + enableTemporaryFilesDeletionOnExit(true); + if (!properties.containsKey(TEMPORARY_FILES_MAX_AGE_PARAMETER)) setTemporaryFilesMaxAge(7); + if (!properties.containsKey(AUTO_SAVING_ENABLED_PARAMETER)) enableAutoSaving(false); + if (!properties.containsKey(AUTO_SAVING_INTERVAL_PARAMETER)) setAutoSavingInterval(5); + } + } + + public synchronized static void fillLoggingConfigurationFileWithDefaultValue() { + if (getLoggingConfigFile().exists()) { + final Properties properties = readAllPropertiesFromConfigurationFile(getLoggingConfigFile()); + + if (!properties.containsKey(LOG_LEVEL_PARAMETER)) setLogLevel(Level.INFO); + if (!properties.containsKey(LOG_HANDLERS_PARAMETER)) + setLogHandler(FileHandler.class, SlideshowFXHandler.class); + if (!properties.containsKey(LOG_FILE_APPEND_PARAMETER)) setLogFileAppend(true); + if (!properties.containsKey(FileHandler.class.getName().concat(LOG_ENCODING_SUFFIX))) + setLogEncoding(FileHandler.class, UTF_8); + if (!properties.containsKey(FileHandler.class.getName().concat(LOG_FORMATTER_SUFFIX))) + setLogFormatter(FileHandler.class, SimpleFormatter.class); + if (!properties.containsKey(LOG_FILE_LIMIT_PARAMETER)) setLogFileLimit(50000); + if (!properties.containsKey(LOG_FILE_PATTERN_PARAMETER)) setLogFilePattern("%h/.SlideshowFX/sfx%g.log"); + if (!properties.containsKey(SlideshowFXHandler.class.getName().concat(LOG_ENCODING_SUFFIX))) + setLogEncoding(SlideshowFXHandler.class, UTF_8); + if (!properties.containsKey(SlideshowFXHandler.class.getName().concat(LOG_FORMATTER_SUFFIX))) + setLogFormatter(SlideshowFXHandler.class, SimpleFormatter.class); + } + } + + /** + * Check if the temporary files can be deleted or not. Temporary files can be deleted if the value of the parameter + * {@link #TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER} is not {@code null] and {@code true} and the value of the + * parameter {@link #TEMPORARY_FILES_MAX_AGE_PARAMETER} is not {@code null}. + * + * @return {@code true} if the temporary files can be deleted, {@code false} otherwise. + */ + public static boolean canDeleteTemporaryFiles() { + final Boolean deleteTemporaryFilesOnExist = getBooleanProperty(TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER); + final Long maxAge = getLongProperty(TEMPORARY_FILES_MAX_AGE_PARAMETER); + + return deleteTemporaryFilesOnExist != null && deleteTemporaryFilesOnExist && maxAge != null; + } + + /** + * Read all properties stored in the configuration file. If no properties are found or if the configuration file + * doesn't exist, an empty object is returned. + * + * @return The properties stored in the configuration file. + */ + protected synchronized static Properties readAllPropertiesFromConfigurationFile(final File file) { + final Properties properties = new Properties(); + + if (file.exists()) { + + try (final Reader reader = new FileReader(file)) { + properties.load(reader); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can not load configuration file: " + file.getAbsolutePath(), e); + } + } + + return properties; + } + + /** + * Writes all properties to the configuration file. If the given properties are null, nothing is performed. + * + * @param file The file in which the properties will be written. + * @param properties The properties to write to the configuration file. + */ + private synchronized static void writeAllPropertiesToConfigurationFile(final File file, final Properties properties) { + if (properties != null) { + try (final Writer writer = new FileWriter(file)) { + properties.store(writer, ""); + writer.flush(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can not save configuration", e); + } + } + } + + /** + * Get a property from the configuration. This methods return {@code null} is the property + * is not found or if the configuration file does not exist. + * + * @param propertyName The name of the property to retrieve. + * @return The value of the property or {@code null} if it is not found or the configuration does not exist. + * @throws NullPointerException If the property name is null. + * @throws IllegalArgumentException If the property name is empty. + */ + public synchronized static String getProperty(final String propertyName) { + return getProperty(getConfigurationFile(), propertyName); + } + + /** + * Get a property from the given {@code file}. This methods return {@code null} is the property + * is not found or if the configuration file does not exist. + * + * @param file The file from which the property will be read. + * @param propertyName The name of the property to retrieve. + * @return The value of the property or {@code null} if it is not found or the configuration does not exist. + * @throws NullPointerException If the property name is null. + * @throws IllegalArgumentException If the property name is empty. + */ + public synchronized static String getProperty(final File file, final String propertyName) { + checkPropertyName(propertyName); + + String value = null; + + if (file.exists()) { + final Properties properties = readAllPropertiesFromConfigurationFile(file); + value = properties.getProperty(propertyName.trim()); + } + + return value; + } + + /** + * Save the given {@code propertyName} and {@code propertyValue} to the configuration. + * + * @param propertyName The name of the property to save. + * @param propertyValue The value of the property to save. + * @throws NullPointerException If the name or value of the property is null. + * @throws IllegalArgumentException If the name or value of the property is empty. + */ + public synchronized static void setProperty(final String propertyName, final String propertyValue) { + setProperty(getConfigurationFile(), propertyName, propertyValue); + } + + /** + * Save the given {@code propertyName} and {@code propertyValue} to the given {@code file}. + * + * @param file The file in which the property will be set. + * @param propertyName The name of the property to save. + * @param propertyValue The value of the property to save. + * @throws NullPointerException If the name or value of the property is null. + * @throws IllegalArgumentException If the name or value of the property is empty. + */ + private synchronized static void setProperty(final File file, final String propertyName, final String propertyValue) { + checkPropertyName(propertyName); + checkPropertyValue(propertyValue); + + final Properties properties = readAllPropertiesFromConfigurationFile(file); + properties.put(propertyName.trim(), propertyValue); + writeAllPropertiesToConfigurationFile(file, properties); + } + + /** + * Remove a property from the configuration file. If the property doesn't exist, nothing is performed. + * + * @param propertyName The name of the property to remove. + */ + public synchronized static void removeProperty(final String propertyName) { + removeProperty(getConfigurationFile(), propertyName); + } + + /** + * Remove a property from the configuration file. If the property doesn't exist, nothing is performed. + * + * @param file The file from which the property will be removed. + * @param propertyName The name of the property to remove. + */ + public synchronized static void removeProperty(final File file, final String propertyName) { + checkPropertyName(propertyName); + + final Properties properties = readAllPropertiesFromConfigurationFile(file); + + if (properties.containsKey(propertyName.trim())) { + properties.remove(propertyName.trim()); + writeAllPropertiesToConfigurationFile(file, properties); + } + } + + /** + * Check if the given property name is valid or not. The property name is considered valid if if is not {@code null} + * and its value is not empty. + * + * @param propertyName The name of the property to check. + * @throws NullPointerException If the property name is {@code null}. + * @throws IllegalArgumentException If the property name is empty. + */ + private static void checkPropertyName(final String propertyName) { + if (propertyName == null) throw new NullPointerException("The property name can not be null"); + if (propertyName.trim().isEmpty()) throw new IllegalArgumentException("The property name can not be empty"); + } + + /** + * Check if the given property name is valid or not. The property name is considered valid if if is not {@code null} + * and its value is not empty. + * + * @param propertyValue The value to check. + * @throws NullPointerException If the property value is {@code null}. + * @throws IllegalArgumentException If the property value is empty. + */ + private static void checkPropertyValue(final String propertyValue) { + if (propertyValue == null) throw new NullPointerException("The property value can not be null"); + if (propertyValue.trim().isEmpty()) throw new IllegalArgumentException("The property value can not be empty"); + } + + /** + * Get the value of a property as a {@link Long}. + * + * @param propertyName The name of the property to get. + * @return The value of the property or {@code null} if it is not present or can not be parsed. + */ + public static Long getLongProperty(final String propertyName) { + return getLongProperty(getConfigurationFile(), propertyName); + } + + /** + * Get the value of a property as a {@link Long}. + * + * @param file The file from which retrieve the property. + * @param propertyName The name of the property to get. + * @return The value of the property or {@code null} if it is not present or can not be parsed. + */ + public static Long getLongProperty(final File file, final String propertyName) { + Long value = null; + + final String retrievedProperty = getProperty(file, propertyName); + if (retrievedProperty != null) { + try { + value = Long.parseLong(retrievedProperty); + } catch (NumberFormatException ex) { + LOGGER.log(Level.WARNING, "The value of the property '" + propertyName + "' can not be parsed", ex); + } + } + + return value; + } + + /** + * Get the value of a property as a {@link Boolean}. + * + * @param propertyName The name of the property to get. + * @return The value of the property or {@code null} if it is not present or can not be parsed. + */ + public static Boolean getBooleanProperty(final String propertyName) { + return getBooleanProperty(getConfigurationFile(), propertyName); + } + + /** + * Get the value of a property as a {@link Boolean}. + * + * @param file The file from which retrieve the property. + * @param propertyName The name of the property to get. + * @return The value of the property or {@code null} if it is not present or can not be parsed. + */ + public static Boolean getBooleanProperty(final File file, final String propertyName) { + Boolean value = null; + + final String retrievedProperty = getProperty(file, propertyName); + if (retrievedProperty != null) { + try { + value = Boolean.parseBoolean(retrievedProperty); + } catch (NumberFormatException ex) { + LOGGER.log(Level.WARNING, "The value of the property '" + propertyName + "' can not be parsed", ex); + } + } + + return value; + } + + /** + * Check if the auto saving is enabled on exit. + * + * @return {@code true} if the auto saving is enabled, {@code false} otherwise. + */ + public static boolean isAutoSavingEnabled() { + final Boolean autoSave = getBooleanProperty(AUTO_SAVING_ENABLED_PARAMETER); + return autoSave == null ? Boolean.FALSE : autoSave; + } + + /** + * Enable or disable the auto saving configuration.. + * + * @param enabled The value of the parameter. + */ + public static void enableAutoSaving(final boolean enabled) { + setProperty(AUTO_SAVING_ENABLED_PARAMETER, String.valueOf(enabled)); + } + + /** + * Get the interval for auto saving files. + * + * @return The interval in minutes. + */ + public static Long getAutoSavingInterval() { + final Long intervalInSeconds = getLongProperty(AUTO_SAVING_INTERVAL_PARAMETER); + return intervalInSeconds == null ? null : TimeUnit.SECONDS.toMinutes(intervalInSeconds); + } + + /** + * Set the auto saving interval configuration parameter. + * + * @param intervalInMinutes The interval in minutes for the auto saving parameter. + */ + public static void setAutoSavingInterval(final long intervalInMinutes) { + setProperty(AUTO_SAVING_INTERVAL_PARAMETER, String.valueOf(TimeUnit.MINUTES.toSeconds(intervalInMinutes))); + } + + /** + * Removes the auto saving interval from the configuration. + */ + public static void removeAutoSavingInterval() { + removeProperty(AUTO_SAVING_INTERVAL_PARAMETER); + } + + /** + * Check if the temporary files deletion is enabled on exit. + * + * @return {@code true} if the deletion is enabled, {@code false} otherwise. + */ + public static boolean isTemporaryFilesDeletionOnExitEnabled() { + final Boolean deleteTemporaryFiles = getBooleanProperty(TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER); + return deleteTemporaryFiles == null ? false : deleteTemporaryFiles; + } + + /** + * Sets the default log level of the application. + * + * @param level The desired log level. + */ + public static void setLogLevel(final Level level) { + setProperty(getLoggingConfigFile(), LOG_LEVEL_PARAMETER, level.getName()); + } + + /** + * Sets the default log handlers. + * + * @param handlers The handlers of logs. + */ + public static void setLogHandler(final Class... handlers) { + final StringJoiner joiner = new StringJoiner(" "); + Arrays.stream(handlers).forEach(handler -> joiner.add(handler.getName())); + + setProperty(getLoggingConfigFile(), LOG_HANDLERS_PARAMETER, joiner.toString()); + } + + /** + * Sets the encoding of log files. + * + * @param handler The class handler to set the encoding for. + * @param charset The encoding of log files. + */ + public static void setLogEncoding(final Class handler, final Charset charset) { + setProperty(getLoggingConfigFile(), handler.getName().concat(LOG_ENCODING_SUFFIX), charset.displayName()); + } + + /** + * Sets the size in bytes of the log files. + * + * @param size The size, in bytes, of log files. + */ + public static void setLogFileLimit(final long size) { + setProperty(getLoggingConfigFile(), LOG_FILE_LIMIT_PARAMETER, String.valueOf(size)); + } + + /** + * Sets the log files pattern. + * + * @param pattern The pattern of log files. + */ + public static void setLogFilePattern(final String pattern) { + setProperty(getLoggingConfigFile(), LOG_FILE_PATTERN_PARAMETER, pattern); + } + + /** + * Sets the class responsible of formatting log files. + * + * @param handler The class handler to set the formatter for. + * @param formatter The formatter to use for log files. + */ + public static void setLogFormatter(final Class handler, final Class formatter) { + setProperty(getLoggingConfigFile(), handler.getName().concat(LOG_FORMATTER_SUFFIX), formatter.getName()); + } + + /** + * Defines if the logs should be append or not to the log files. + * + * @param append {@code true} to allow appending, {@code false} otherwise. + */ + public static void setLogFileAppend(final boolean append) { + setProperty(getLoggingConfigFile(), LOG_FILE_APPEND_PARAMETER, String.valueOf(append)); + } + + /** + * Enable or disable the temporary files deletion. + * + * @param enable {@code true} to enable the deletion, {@code false} otherwise. + */ + public static void enableTemporaryFilesDeletionOnExit(final boolean enable) { + setProperty(TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER, String.valueOf(enable)); + } + + /** + * Get the temporary files max age parameter's value. + * + * @return The max age of temporary files in days. + */ + public static Long getTemporaryFilesMaxAge() { + final Long ageInSeconds = getLongProperty(TEMPORARY_FILES_MAX_AGE_PARAMETER); + return ageInSeconds == null ? null : TimeUnit.SECONDS.toDays(ageInSeconds); + } + + /** + * Set the max age of temporary files before they are deleted. + * + * @param maxAgeInDays The max age of the temporary files. + */ + public static void setTemporaryFilesMaxAge(final long maxAgeInDays) { + setProperty(TEMPORARY_FILES_MAX_AGE_PARAMETER, String.valueOf(TimeUnit.DAYS.toSeconds(maxAgeInDays))); + } + + /** + * Remove the temporary files max age from the configuration. + */ + public static void removeTemporaryFilesMaxAge() { + removeProperty(TEMPORARY_FILES_MAX_AGE_PARAMETER); + } + + /** + * Get the default max recent presentations that must be stored and displayed in the "Open recent" menu. + * + * @return The default max recent presentations. + */ + public static Long getDefaultMaxRecentPresentations() { + return DEFAULT_MAX_RECENT_PRESENTATIONS; + } + + /** + * Get the maximum number of recent presentations that must be stored and displayed in the "Open recent" menu. + * + * @return The maximum number of recent presentations. + */ + public static Long getMaxRecentPresentations() { + final Long maxRecentPresentations = getLongProperty(MAX_RECENT_PRESENTATIONS); + return maxRecentPresentations == null ? getDefaultMaxRecentPresentations() : maxRecentPresentations; + } + + /** + * Set the maximum number of recent presentations that must be stored and displayed in the "Open recent" menu. + * + * @param maxRecentPresentations The maximum number of recent presentations. + */ + public static void setMaxRecentPresentations(final long maxRecentPresentations) { + setProperty(MAX_RECENT_PRESENTATIONS, String.valueOf(maxRecentPresentations)); + } + + /** + * Remove the maximum number of recent presentations that must be stored and displayed in the "Open recent" menu. + */ + public static void removeMaxRecentPresentations() { + removeProperty(MAX_RECENT_PRESENTATIONS); + } + + /** + * Get the Twitter consumer key. + * + * @return The Twitter consumer key stored in the configuration. + */ + public static String getTwitterConsumerKey() { + return getProperty(TWITTER_CONSUMER_KEY); + } + + /** + * Set the Twitter consumer key. + * + * @param twitterConsumerKey The Twitter consumer key to store. + */ + public static void setTwitterConsumerKey(final String twitterConsumerKey) { + setProperty(TWITTER_CONSUMER_KEY, twitterConsumerKey); + } + + /** + * Get the Twitter consumer secret. + * + * @return The Twitter consumer secret stored in the configuration. + */ + public static String getTwitterConsumerSecret() { + return getProperty(TWITTER_CONSUMER_SECRET); + } + + /** + * Set the Twitter consumer secret. + * + * @param twitterConsumerSecret The Twitter consumer secret to store. + */ + public static void setTwitterConsumerSecret(final String twitterConsumerSecret) { + setProperty(TWITTER_CONSUMER_SECRET, twitterConsumerSecret); + } + + /** + * Get the default {@link Charset} used by the application. + * + * @return The default charset used by the application. + */ + public static Charset getDefaultCharset() { + return DEFAULT_CHARSET; + } + + /** + * Get a collection of presentations opened recently. + * + * @return The collection of presentations opened recently. + */ + public synchronized static Set getRecentPresentations() { + if (RECENT_PRESENTATIONS == null) { + try { + RECENT_PRESENTATIONS = ContextFileWorker.readRecentPresentationFromFile(new File(getApplicationDirectory(), SLIDESHOWFX_CONTEXT_FILE_NAME)); + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "Can not read the recent opened presentations", e); + } + } + final Long maxRecentPresentations = getMaxRecentPresentations(); + + synchronized (RECENT_PRESENTATIONS) { + if (RECENT_PRESENTATIONS.size() > maxRecentPresentations) { + final File contextFile = new File(getApplicationDirectory(), SLIDESHOWFX_CONTEXT_FILE_NAME); + try { + RECENT_PRESENTATIONS = ContextFileWorker.purgeRecentPresentations(contextFile, maxRecentPresentations); + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "Can not purge recent presentations", e); + } + } + } + + return RECENT_PRESENTATIONS; + } + + /** + * Save a {@link RecentPresentation} as a recently opened presentation. This save is persisted on disk. + * + * @param recentPresentation The presentation to save as recently opened. + */ + public synchronized static void saveRecentPresentation(final RecentPresentation recentPresentation) { + if (recentPresentation != null) { + final File contextFile = new File(getApplicationDirectory(), SLIDESHOWFX_CONTEXT_FILE_NAME); + boolean presentationAlreadyPresent = false; + + try { + presentationAlreadyPresent = ContextFileWorker.recentPresentationAlreadyPresent(contextFile, recentPresentation); + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "Context file seems to not exist", e); + } + + synchronized (RECENT_PRESENTATIONS) { + if (RECENT_PRESENTATIONS.contains(recentPresentation)) { + RECENT_PRESENTATIONS.remove(recentPresentation); + } + } + + if (presentationAlreadyPresent) { + try { + synchronized (RECENT_PRESENTATIONS) { + ContextFileWorker.updateRecentPresentationInFile(contextFile, recentPresentation); + RECENT_PRESENTATIONS.add(recentPresentation); + } + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "Can not update recently opened presentation", e); + } + } else { + try { + synchronized (RECENT_PRESENTATIONS) { + ContextFileWorker.saveRecentPresentationToFile(contextFile, recentPresentation); + RECENT_PRESENTATIONS.add(recentPresentation); + } + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "The recent presentation couldn't be saved", e); + } + } + + synchronized (RECENT_PRESENTATIONS) { + final Long maxRecentPresentations = getMaxRecentPresentations(); + if (RECENT_PRESENTATIONS.size() > maxRecentPresentations) { + try { + RECENT_PRESENTATIONS = ContextFileWorker.purgeRecentPresentations(contextFile, maxRecentPresentations); + } catch (ContextFileException e) { + LOGGER.log(Level.WARNING, "Can not purge recent presentations", e); + } + } + } + } + } +} diff --git a/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/RecentPresentation.java b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/RecentPresentation.java new file mode 100755 index 00000000..aea75ed9 --- /dev/null +++ b/slideshowfx-global-configuration/src/main/java/com/twasyl/slideshowfx/global/configuration/RecentPresentation.java @@ -0,0 +1,101 @@ +package com.twasyl.slideshowfx.global.configuration; + +import java.io.File; +import java.time.LocalDateTime; +import java.util.Base64; + +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.getDefaultCharset; + +/** + * Represent a presentation opened recently by SlideshowFX. + * + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 2.0 + */ +public class RecentPresentation extends File implements Comparable { + private LocalDateTime openedDateTime; + private String normalizedPath; + private String id; + + public RecentPresentation(final String path, final LocalDateTime openedDateTime) { + super(path); + this.openedDateTime = openedDateTime; + } + + public RecentPresentation(final String parent, final String child, final LocalDateTime openedDateTime) { + super(parent, child); + this.openedDateTime = openedDateTime; + } + + public RecentPresentation(final File parent, final String child, final LocalDateTime openedDateTime) { + super(parent, child); + this.openedDateTime = openedDateTime; + } + + public LocalDateTime getOpenedDateTime() { + return openedDateTime; + } + + public void setOpenedDateTime(LocalDateTime openedDateTime) { + this.openedDateTime = openedDateTime; + } + + /** + * Get the ID of this recent presentation. The ID is the value returned by {@link #getNormalizedPath()} encoded in + * Base 64. + * + * @return The ID of this recent presentation. + */ + public String getId() { + if (this.id == null) { + this.id = Base64.getEncoder().encodeToString(this.getNormalizedPath().getBytes(getDefaultCharset())); + } + + return this.id; + } + + /** + * Get the normalized path of this recent presentation. The normalization consists of replacing all back slashes + * by forward slashes in the {@link #getAbsolutePath() absolute path} of this recent presentation. + * + * @return The normalized path of this recent presentation. + */ + public String getNormalizedPath() { + if (normalizedPath == null) { + this.normalizedPath = getAbsolutePath().replaceAll("\\\\", "/"); + } + + return this.normalizedPath; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + final RecentPresentation that = (RecentPresentation) o; + + return getNormalizedPath() != null ? getNormalizedPath().equals(that.getNormalizedPath()) : that.getNormalizedPath() == null; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (getNormalizedPath() != null ? getNormalizedPath().hashCode() : 0); + return result; + } + + @Override + public int compareTo(File o) { + if (o == null) { + return 1; + } else if (o instanceof RecentPresentation) { + final RecentPresentation other = (RecentPresentation) o; + return this.getNormalizedPath().compareTo(other.getNormalizedPath()); + } else { + return 0; + } + } +} diff --git a/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/ContextFileWorkerTest.java b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/ContextFileWorkerTest.java new file mode 100755 index 00000000..7f8b6c56 --- /dev/null +++ b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/ContextFileWorkerTest.java @@ -0,0 +1,531 @@ +package com.twasyl.slideshowfx.global.configuration; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.Base64; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static com.twasyl.slideshowfx.global.configuration.ContextFileWorker.*; +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.xml.xpath.XPathConstants.NODE; +import static javax.xml.xpath.XPathConstants.STRING; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public class ContextFileWorkerTest { + + protected Node createIdNode(final String id) { + final Node node = mock(Node.class); + when(node.getNodeName()).thenReturn(ID_TAG); + when(node.getTextContent()).thenReturn(id); + + return node; + } + + protected Node createFileNode(final String path) { + final Node node = mock(Node.class); + when(node.getNodeName()).thenReturn(FILE_TAG); + when(node.getTextContent()).thenReturn(path); + + return node; + } + + protected Node createOpenedDateTimeNode(final LocalDateTime openedDateTime) { + final Node node = mock(Node.class); + when(node.getNodeName()).thenReturn(OPENED_DATE_TIME_TAG); + when(node.getTextContent()).thenReturn(openedDateTime.toString()); + + return node; + } + + protected Node createRecentPresentationNode(final Node... children) { + final Node node = mock(Node.class); + when(node.getNodeName()).thenReturn(RECENT_PRESENTATION_TAG); + + final NodeList list = mock(NodeList.class); + when(list.getLength()).thenReturn(children.length); + when(list.item(anyInt())).then(invocation -> children[(int) invocation.getArgument(0)]); + + when(node.getChildNodes()).thenReturn(list); + + return node; + } + + protected Document createDocumentFromString(final String xml) throws ParserConfigurationException, IOException, SAXException { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.parse(new ByteArrayInputStream(xml.getBytes())); + return document; + } + + protected String createXmlStringFromRecentPresentations(final RecentPresentation... recentPresentations) { + final StringBuilder xml = new StringBuilder(""); + + for (RecentPresentation recentPresentation : recentPresentations) { + xml.append("") + .append("").append(recentPresentation.getId()).append("") + .append("").append(recentPresentation.getNormalizedPath()).append("") + .append("").append(recentPresentation.getOpenedDateTime()).append("") + .append(""); + } + + xml.append(""); + + return xml.toString(); + } + + protected void assertRecentPresentationNode(final Node createdNode, final RecentPresentation recentPresentation) throws XPathExpressionException { + assertNotNull(createdNode); + assertEquals(RECENT_PRESENTATION_TAG, createdNode.getNodeName()); + assertTrue(createdNode.hasChildNodes()); + + final XPathFactory xPathFactory = XPathFactory.newInstance(); + final XPath xPath = xPathFactory.newXPath(); + + final Node idNode = (Node) xPath.evaluate("./" + ID_TAG, createdNode, NODE); + assertNotNull(idNode); + assertEquals(recentPresentation.getId(), idNode.getTextContent()); + + final Node fileNode = (Node) xPath.evaluate("./" + FILE_TAG, createdNode, NODE); + assertNotNull(fileNode); + assertEquals(recentPresentation.getNormalizedPath(), fileNode.getTextContent()); + + final Node openedDateTimeNode = (Node) xPath.evaluate("./" + OPENED_DATE_TIME_TAG, createdNode, NODE); + assertNotNull(openedDateTimeNode); + assertEquals(recentPresentation.getOpenedDateTime().toString(), openedDateTimeNode.getTextContent()); + } + + @Test + public void createRecentPresentationFromNode() { + final LocalDateTime date = LocalDateTime.now(); + final Path path = Paths.get("presentation.sfx"); + final String normalizedPath = path.toAbsolutePath().toString().replaceAll("\\\\", "/"); + final String id = Base64.getEncoder().encodeToString(normalizedPath.getBytes(UTF_8)); + + final Node node = createRecentPresentationNode(createIdNode(id), createFileNode(path.toAbsolutePath().toString()), createOpenedDateTimeNode(date)); + final RecentPresentation recentPresentation = ContextFileWorker.buildRecentPresentationFromNode(node); + + assertNotNull(recentPresentation); + assertEquals(normalizedPath, recentPresentation.getNormalizedPath()); + assertEquals(date, recentPresentation.getOpenedDateTime()); + } + + @Test + public void createRecentPresentationFromNodeWithoutPath() { + final LocalDateTime date = LocalDateTime.now(); + + final Node node = createRecentPresentationNode(createOpenedDateTimeNode(date)); + final RecentPresentation recentPresentation = ContextFileWorker.buildRecentPresentationFromNode(node); + + assertNull(recentPresentation); + } + + @Test + public void createRecentPresentationFromNodeWithoutOpenedDate() { + final String path = "/presentation.sfx"; + + final Node node = createRecentPresentationNode(createFileNode(path)); + final RecentPresentation recentPresentation = ContextFileWorker.buildRecentPresentationFromNode(node); + + assertNull(recentPresentation); + } + + @Test + public void createRecentPresentationFromNodeWithoutChildren() { + final Node node = createRecentPresentationNode(); + final RecentPresentation recentPresentation = ContextFileWorker.buildRecentPresentationFromNode(node); + + assertNull(recentPresentation); + } + + @Test + public void getRecentPresentationsNode() throws Exception { + final String xml = ""; + final Document document = createDocumentFromString(xml); + + final Node recentPresentationsNode = ContextFileWorker.getRecentPresentationsNode(document); + + assertNotNull(recentPresentationsNode); + assertEquals(RECENT_PRESENTATIONS_TAG, recentPresentationsNode.getNodeName()); + } + + @Test + public void tryToGetRecentPresentationsNodeWhenMissing() throws Exception { + final String xml = ""; + final Document document = createDocumentFromString(xml); + + final Node recentPresentationsNode = ContextFileWorker.getRecentPresentationsNode(document); + + assertNull(recentPresentationsNode); + } + + @Test + public void tryToGetRecentPresentationNodesWhenMissing() throws Exception { + final String xml = ""; + final Document document = createDocumentFromString(xml); + + final Node recentPresentationsNode = ContextFileWorker.getRecentPresentationsNode(document); + final List recentPresentationNodes = ContextFileWorker.getRecentPresentationNodes(recentPresentationsNode); + + assertNotNull(recentPresentationNodes); + assertEquals(0, recentPresentationNodes.size()); + } + + @Test + public void getRecentPresentations() throws Exception { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + + final Document document = createDocumentFromString(this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2)); + final List recentPresentationNodes = ContextFileWorker.getRecentPresentationNodes(ContextFileWorker.getRecentPresentationsNode(document)); + + assertNotNull(recentPresentationNodes); + assertEquals(2, recentPresentationNodes.size()); + } + + @Test + public void recentPresentationsCorrectlyCreated() throws Exception { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + + final Document document = createDocumentFromString(this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2)); + final List recentPresentationNodes = ContextFileWorker.getRecentPresentationNodes(ContextFileWorker.getRecentPresentationsNode(document)); + + RecentPresentation recentPresentation = ContextFileWorker.buildRecentPresentationFromNode(recentPresentationNodes.get(0)); + assertNotNull(recentPresentation); + assertEquals(recentPresentation1.getNormalizedPath(), recentPresentation.getNormalizedPath()); + assertEquals(recentPresentation1.getOpenedDateTime(), recentPresentation.getOpenedDateTime()); + assertEquals(recentPresentation1.getId(), recentPresentation.getId()); + + recentPresentation = ContextFileWorker.buildRecentPresentationFromNode(recentPresentationNodes.get(1)); + assertNotNull(recentPresentation); + assertEquals(recentPresentation2.getNormalizedPath(), recentPresentation.getNormalizedPath()); + assertEquals(recentPresentation2.getOpenedDateTime(), recentPresentation.getOpenedDateTime()); + assertEquals(recentPresentation2.getId(), recentPresentation.getId()); + } + + @Test + public void recentPresentationsAreSortedByNormalizedPath() { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + final RecentPresentation recentPresentation3 = new RecentPresentation("presentation3.sfx", recentPresentation2.getOpenedDateTime().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation3, recentPresentation1, recentPresentation2); + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes()); + final Set presentations = ContextFileWorker.readRecentPresentationFromStream(input); + + final Iterator iterator = presentations.iterator(); + RecentPresentation recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation1.getNormalizedPath(), recentPresentation.getNormalizedPath()); + assertEquals(recentPresentation1.getOpenedDateTime(), recentPresentation.getOpenedDateTime()); + assertEquals(recentPresentation1.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation2.getNormalizedPath(), recentPresentation.getNormalizedPath()); + assertEquals(recentPresentation2.getOpenedDateTime(), recentPresentation.getOpenedDateTime()); + assertEquals(recentPresentation2.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation3.getNormalizedPath(), recentPresentation.getNormalizedPath()); + assertEquals(recentPresentation3.getOpenedDateTime(), recentPresentation.getOpenedDateTime()); + assertEquals(recentPresentation3.getId(), recentPresentation.getId()); + } + + @Test + public void saveWithEmptyDocument() throws Exception { + final String path = "presentation.sfx"; + final LocalDateTime openedDateTime = LocalDateTime.now(); + + final RecentPresentation recentPresentation = new RecentPresentation(path, openedDateTime); + final ByteArrayInputStream input = new ByteArrayInputStream(new byte[0]); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + ContextFileWorker.saveRecentPresentation(input, output, recentPresentation); + + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.parse(new ByteArrayInputStream(output.toByteArray())); + + final XPathFactory xPathFactory = XPathFactory.newInstance(); + final XPath xPath = xPathFactory.newXPath(); + + final String baseExpr = new StringBuilder("/").append(ROOT_TAG).append("/").append(RECENT_PRESENTATIONS_TAG).append("/").append(RECENT_PRESENTATION_TAG).append("[1]").toString(); + final String actualFile = (String) xPath.evaluate(baseExpr + "/" + FILE_TAG, document.getDocumentElement(), STRING); + final String actualId = (String) xPath.evaluate(baseExpr + "/" + ID_TAG, document.getDocumentElement(), STRING); + + assertNotNull(actualId); + assertEquals(recentPresentation.getId(), actualId); + + assertNotNull(actualFile); + assertEquals(recentPresentation.getNormalizedPath(), actualFile); + + final LocalDateTime actualOpenedDateTime = LocalDateTime.parse((String) xPath.evaluate(baseExpr + "/" + OPENED_DATE_TIME_TAG, document.getDocumentElement(), STRING)); + assertEquals(openedDateTime, actualOpenedDateTime); + } + + @Test + public void saveWithExistingDocument() throws Exception { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", LocalDateTime.now().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + ContextFileWorker.saveRecentPresentation(input, output, recentPresentation2); + + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.parse(new ByteArrayInputStream(output.toByteArray())); + + final XPathFactory xPathFactory = XPathFactory.newInstance(); + final XPath xPath = xPathFactory.newXPath(); + + final String baseExpr = new StringBuilder("/").append(ROOT_TAG).append("/").append(RECENT_PRESENTATIONS_TAG).append("/").append(RECENT_PRESENTATION_TAG).append("[2]").toString(); + final String actualFile = (String) xPath.evaluate(baseExpr + "/" + FILE_TAG, document.getDocumentElement(), STRING); + final String actualId = (String) xPath.evaluate(baseExpr + "/" + ID_TAG, document.getDocumentElement(), STRING); + + assertNotNull(actualId); + assertEquals(recentPresentation2.getId(), actualId); + + assertNotNull(actualFile); + assertEquals(recentPresentation2.getNormalizedPath(), actualFile); + + final LocalDateTime actualOpenedDateTime = LocalDateTime.parse((String) xPath.evaluate(baseExpr + "/" + OPENED_DATE_TIME_TAG, document.getDocumentElement(), STRING)); + assertEquals(recentPresentation2.getOpenedDateTime(), actualOpenedDateTime); + } + + @Test + public void populateEmptyDocument() throws Exception { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.newDocument(); + + ContextFileWorker.populateDocumentIfNecessary(document); + + final XPathFactory xPathFactory = XPathFactory.newInstance(); + final XPath xPath = xPathFactory.newXPath(); + + assertNotNull(document.getDocumentElement()); + assertEquals(ROOT_TAG, document.getDocumentElement().getTagName()); + + final Node recentPresentationsNode = (Node) xPath.evaluate("/" + ROOT_TAG + "/" + RECENT_PRESENTATIONS_TAG, document.getDocumentElement(), NODE); + assertNotNull(recentPresentationsNode); + } + + @Test + public void populateDocumentWhenNoRecentPresentationsTag() throws Exception { + final String xml = ""; + final Document document = createDocumentFromString(xml); + + ContextFileWorker.populateDocumentIfNecessary(document); + + final XPathFactory xPathFactory = XPathFactory.newInstance(); + final XPath xPath = xPathFactory.newXPath(); + + final Node recentPresentationsNode = (Node) xPath.evaluate("/" + ROOT_TAG + "/" + RECENT_PRESENTATIONS_TAG, document.getDocumentElement(), NODE); + assertNotNull(recentPresentationsNode); + } + + @Test + public void createNodeFromRecentPresentation() throws Exception { + final String xml = ""; + final Document document = createDocumentFromString(xml); + + final String path = "presentation.sfx"; + final LocalDateTime openedDateTime = LocalDateTime.now(); + + final RecentPresentation recentPresentation = new RecentPresentation(path, openedDateTime); + + final Node createdNode = ContextFileWorker.createNodeFromRecentPresentation(document, recentPresentation); + assertRecentPresentationNode(createdNode, recentPresentation); + } + + @Test + public void findExistingRecentPresentation() throws Exception { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2); + final Document document = createDocumentFromString(xml); + + Node node = ContextFileWorker.findRecentPresentationNodeFromID(document, recentPresentation1); + assertRecentPresentationNode(node, recentPresentation1); + + node = ContextFileWorker.findRecentPresentationNodeFromID(document, recentPresentation2); + assertRecentPresentationNode(node, recentPresentation2); + } + + @Test + public void findRecentPresentationWhenMissing() throws Exception { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + final RecentPresentation recentPresentation3 = new RecentPresentation("presentation3.sfx", recentPresentation2.getOpenedDateTime().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2); + final Document document = createDocumentFromString(xml); + + Node node = ContextFileWorker.findRecentPresentationNodeFromID(document, recentPresentation3); + assertNull(node); + } + + @Test + public void updatePresentation() throws Exception { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", LocalDateTime.now().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + recentPresentation2.setOpenedDateTime(recentPresentation2.getOpenedDateTime().plusDays(2)); + ContextFileWorker.updateRecentPresentation(input, output, recentPresentation2); + + final Document updatedDocument = createDocumentFromString(new String(output.toByteArray(), UTF_8)); + final List nodes = ContextFileWorker.getRecentPresentationNodes(ContextFileWorker.getRecentPresentationsNode(updatedDocument)); + + assertEquals(2, nodes.size()); + + Node recentPresentation = ContextFileWorker.findRecentPresentationNodeFromID(updatedDocument, recentPresentation1); + assertRecentPresentationNode(recentPresentation, recentPresentation1); + + recentPresentation = ContextFileWorker.findRecentPresentationNodeFromID(updatedDocument, recentPresentation2); + assertRecentPresentationNode(recentPresentation, recentPresentation2); + } + + @Test + public void testRecentPresentationAlreadyPresent() { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", LocalDateTime.now().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + + assertTrue(ContextFileWorker.recentPresentationAlreadyPresent(input, recentPresentation2)); + } + + @Test + public void testRecentPresentationAlreadyPresentWhenNot() { + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", LocalDateTime.now().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + + assertFalse(ContextFileWorker.recentPresentationAlreadyPresent(input, recentPresentation2)); + } + + @Test + public void purgeRecentPresentations() throws ContextFileException { + final int numberOfRecentPresentationsToKeep = 2; + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + final RecentPresentation recentPresentation3 = new RecentPresentation("presentation3.sfx", recentPresentation2.getOpenedDateTime().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2, recentPresentation3); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + final Set presentations = ContextFileWorker.purgeRecentPresentations(input, output, numberOfRecentPresentationsToKeep); + assertEquals(numberOfRecentPresentationsToKeep, presentations.size()); + + final Iterator iterator = presentations.iterator(); + RecentPresentation recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation2.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation3.getId(), recentPresentation.getId()); + } + + @Test + public void purgeRecentPresentationsWhenEqualAsSpecified() throws ContextFileException { + final int numberOfRecentPresentationsToKeep = 3; + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + final RecentPresentation recentPresentation3 = new RecentPresentation("presentation3.sfx", recentPresentation2.getOpenedDateTime().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2, recentPresentation3); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + final Set presentations = ContextFileWorker.purgeRecentPresentations(input, output, numberOfRecentPresentationsToKeep); + assertEquals(numberOfRecentPresentationsToKeep, presentations.size()); + + final Iterator iterator = presentations.iterator(); + RecentPresentation recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation1.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation2.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation3.getId(), recentPresentation.getId()); + } + + @Test + public void purgeRecentPresentationsWhenLessThanSpecified() throws ContextFileException { + final int numberOfRecentPresentationsToKeep = 4; + final RecentPresentation recentPresentation1 = new RecentPresentation("presentation1.sfx", LocalDateTime.now()); + final RecentPresentation recentPresentation2 = new RecentPresentation("presentation2.sfx", recentPresentation1.getOpenedDateTime().plusDays(2)); + final RecentPresentation recentPresentation3 = new RecentPresentation("presentation3.sfx", recentPresentation2.getOpenedDateTime().plusDays(2)); + + final String xml = this.createXmlStringFromRecentPresentations(recentPresentation1, recentPresentation2, recentPresentation3); + + final ByteArrayInputStream input = new ByteArrayInputStream(xml.getBytes(UTF_8)); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + + final Set presentations = ContextFileWorker.purgeRecentPresentations(input, output, numberOfRecentPresentationsToKeep); + assertEquals(3, presentations.size()); + + final Iterator iterator = presentations.iterator(); + RecentPresentation recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation1.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation2.getId(), recentPresentation.getId()); + + recentPresentation = iterator.next(); + assertNotNull(recentPresentation); + assertEquals(recentPresentation3.getId(), recentPresentation.getId()); + } +} diff --git a/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationDefaultValuesTest.java b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationDefaultValuesTest.java new file mode 100755 index 00000000..90541a84 --- /dev/null +++ b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationDefaultValuesTest.java @@ -0,0 +1,130 @@ +package com.twasyl.slideshowfx.global.configuration; + +import com.twasyl.slideshowfx.logs.SlideshowFXHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.logging.FileHandler; + +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public class GlobalConfigurationDefaultValuesTest { + + private static File tmpFolder; + private static Properties loggingConfigProperties; + private static Properties applicationProperties; + + @BeforeAll + public static void setUp() { + tmpFolder = new File("build", "testsTmp"); + if (!tmpFolder.exists()) { + tmpFolder.mkdirs(); + } + + applicationProperties = new Properties(); + loggingConfigProperties = new Properties(); + + System.setProperty(APPLICATION_DIRECTORY_PROPERTY, tmpFolder.getAbsolutePath()); + + GlobalConfiguration.createConfigurationFile(); + GlobalConfiguration.createLoggingConfigurationFile(); + + GlobalConfiguration.fillConfigurationWithDefaultValue(); + GlobalConfiguration.fillLoggingConfigurationFileWithDefaultValue(); + + try (final FileInputStream input = new FileInputStream(GlobalConfiguration.getConfigurationFile())) { + applicationProperties.load(input); + } catch (IOException e) { + fail(e.getMessage()); + } + + try (final FileInputStream input = new FileInputStream(GlobalConfiguration.getLoggingConfigFile())) { + loggingConfigProperties.load(input); + } catch (IOException e) { + fail(e.getMessage()); + } + } + + @AfterAll + public static void tearDown() { + GlobalConfiguration.getConfigurationFile().delete(); + GlobalConfiguration.getLoggingConfigFile().delete(); + GlobalConfiguration.getApplicationDirectory().delete(); + } + + @Test + public void hasLogLevelParameter() { + assertTrue(loggingConfigProperties.containsKey(LOG_LEVEL_PARAMETER)); + } + + @Test + public void hasLogHandlersParameter() { + assertTrue(loggingConfigProperties.containsKey(LOG_HANDLERS_PARAMETER)); + } + + @Test + public void hasLogEncodingSuffixForFileHandler() { + assertTrue(loggingConfigProperties.containsKey(FileHandler.class.getName().concat(LOG_ENCODING_SUFFIX))); + } + + @Test + public void hasLogEncodingSuffixForSlideshowFXHandler() { + assertTrue(loggingConfigProperties.containsKey(SlideshowFXHandler.class.getName().concat(LOG_ENCODING_SUFFIX))); + } + + @Test + public void hasLogFileLimitParameter() { + assertTrue(loggingConfigProperties.containsKey(LOG_FILE_LIMIT_PARAMETER)); + } + + @Test + public void hasLogFilePatternParameter() { + assertTrue(loggingConfigProperties.containsKey(LOG_FILE_PATTERN_PARAMETER)); + } + + @Test + public void hasLogFormatterSuffixForFileHandler() { + assertTrue(loggingConfigProperties.containsKey(FileHandler.class.getName().concat(LOG_FORMATTER_SUFFIX))); + } + + @Test + public void hasLogFormatterSuffixForSlideshowFXHandler() { + assertTrue(loggingConfigProperties.containsKey(SlideshowFXHandler.class.getName().concat(LOG_FORMATTER_SUFFIX))); + } + + @Test + public void hasLogFileAppendParameter() { + assertTrue(loggingConfigProperties.containsKey(LOG_FILE_APPEND_PARAMETER)); + } + + @Test + public void hasTemporaryFilesDeletionOnExit() { + assertTrue(applicationProperties.containsKey(TEMPORARY_FILES_DELETION_ON_EXIT_PARAMETER)); + } + + @Test + public void hasTemporaryFilesMaxAge() { + assertTrue(applicationProperties.containsKey(TEMPORARY_FILES_MAX_AGE_PARAMETER)); + } + + @Test + public void hasAutoSavingEnabled() { + assertTrue(applicationProperties.containsKey(AUTO_SAVING_ENABLED_PARAMETER)); + } + + @Test + public void hasAutoSavingInterval() { + assertTrue(applicationProperties.containsKey(AUTO_SAVING_INTERVAL_PARAMETER)); + } +} diff --git a/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationDefinitionTest.java b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationDefinitionTest.java new file mode 100755 index 00000000..93755dfa --- /dev/null +++ b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationDefinitionTest.java @@ -0,0 +1,147 @@ +package com.twasyl.slideshowfx.global.configuration; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.logging.Level; + +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public class GlobalConfigurationDefinitionTest { + + private static File tmpFolder; + + @BeforeAll + public static void setUp() { + tmpFolder = new File("build", "testsTmp"); + if (!tmpFolder.exists()) { + tmpFolder.mkdirs(); + } + + System.setProperty(APPLICATION_DIRECTORY_PROPERTY, tmpFolder.getAbsolutePath()); + + GlobalConfiguration.createConfigurationFile(); + GlobalConfiguration.createLoggingConfigurationFile(); + + GlobalConfiguration.fillConfigurationWithDefaultValue(); + GlobalConfiguration.fillLoggingConfigurationFileWithDefaultValue(); + } + + @AfterAll + public static void tearDown() { + GlobalConfiguration.getConfigurationFile().delete(); + GlobalConfiguration.getLoggingConfigFile().delete(); + GlobalConfiguration.getApplicationDirectory().delete(); + } + + @Test + public void autoSavingEnabled() { + GlobalConfiguration.enableAutoSaving(true); + assertTrue(GlobalConfiguration.isAutoSavingEnabled()); + } + + @Test + public void autoSavingDisabled() { + GlobalConfiguration.enableAutoSaving(false); + assertFalse(GlobalConfiguration.isAutoSavingEnabled()); + } + + @Test + public void autoSavingInterval100() { + GlobalConfiguration.setAutoSavingInterval(100); + assertEquals(100l, (long) GlobalConfiguration.getAutoSavingInterval()); + } + + @Test + public void autoSavingInterval500() { + GlobalConfiguration.setAutoSavingInterval(500); + assertEquals(500l, (long) GlobalConfiguration.getAutoSavingInterval()); + } + + @Test + public void temporaryFilesDeletionOnExitEnabled() { + GlobalConfiguration.enableTemporaryFilesDeletionOnExit(true); + assertTrue(GlobalConfiguration.isTemporaryFilesDeletionOnExitEnabled()); + } + + @Test + public void temporaryFilesDeletionOnExitDisabled() { + GlobalConfiguration.enableTemporaryFilesDeletionOnExit(false); + assertFalse(GlobalConfiguration.isTemporaryFilesDeletionOnExitEnabled()); + } + + @Test + public void temporaryFilesMaxAge100() { + GlobalConfiguration.setTemporaryFilesMaxAge(100); + assertEquals(100l, (long) GlobalConfiguration.getTemporaryFilesMaxAge()); + } + + @Test + public void temporaryFilesMaxAge500() { + GlobalConfiguration.setTemporaryFilesMaxAge(500); + assertEquals(500l, (long) GlobalConfiguration.getTemporaryFilesMaxAge()); + } + + @Test + public void setInfoLogLevel() { + GlobalConfiguration.setLogLevel(Level.INFO); + + final String property = GlobalConfiguration.getProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_LEVEL_PARAMETER); + assertEquals(Level.INFO.getName(), property); + } + + @Test + public void setSevereLogLevel() { + GlobalConfiguration.setLogLevel(Level.SEVERE); + + final String property = GlobalConfiguration.getProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_LEVEL_PARAMETER); + assertEquals(Level.SEVERE.getName(), property); + } + + @Test + public void enableLogFileAppend() { + GlobalConfiguration.setLogFileAppend(true); + + final Boolean property = GlobalConfiguration.getBooleanProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_FILE_APPEND_PARAMETER); + assertTrue(property); + } + + @Test + public void disableLogFileAppend() { + GlobalConfiguration.setLogFileAppend(false); + + final Boolean property = GlobalConfiguration.getBooleanProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_FILE_APPEND_PARAMETER); + assertFalse(property); + } + + @Test + public void setLogFileLimit100() { + GlobalConfiguration.setLogFileLimit(100); + + final Long property = GlobalConfiguration.getLongProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_FILE_LIMIT_PARAMETER); + assertEquals(100l, (long) property); + } + + @Test + public void setLogFileLimit500() { + GlobalConfiguration.setLogFileLimit(500); + + final Long property = GlobalConfiguration.getLongProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_FILE_LIMIT_PARAMETER); + assertEquals(500l, (long) property); + } + + @Test + public void setLogFilePattern() { + GlobalConfiguration.setLogFilePattern("abc"); + + final String property = GlobalConfiguration.getProperty(GlobalConfiguration.getLoggingConfigFile(), LOG_FILE_PATTERN_PARAMETER); + assertEquals("abc", property); + } +} diff --git a/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationFilesAndFoldersCreationTest.java b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationFilesAndFoldersCreationTest.java new file mode 100755 index 00000000..02306860 --- /dev/null +++ b/slideshowfx-global-configuration/src/test/java/com/twasyl/slideshowfx/global/configuration/GlobalConfigurationFilesAndFoldersCreationTest.java @@ -0,0 +1,77 @@ +package com.twasyl.slideshowfx.global.configuration; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.APPLICATION_DIRECTORY_PROPERTY; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public class GlobalConfigurationFilesAndFoldersCreationTest { + + private static File tmpFolder; + + @BeforeAll + public static void setUp() { + final File buildFolder = new File("build"); + if (!buildFolder.exists()) { + buildFolder.mkdirs(); + } + + tmpFolder = new File(buildFolder, "testsTmp"); + + System.setProperty(APPLICATION_DIRECTORY_PROPERTY, tmpFolder.getAbsolutePath()); + } + + @AfterEach + public void after() { + if (GlobalConfiguration.getConfigurationFile().exists()) { + GlobalConfiguration.getConfigurationFile().delete(); + } + + if (GlobalConfiguration.getLoggingConfigFile().exists()) { + GlobalConfiguration.getLoggingConfigFile().delete(); + } + + if (GlobalConfiguration.getPluginsDirectory().exists()) { + GlobalConfiguration.getPluginsDirectory().delete(); + } + + if (GlobalConfiguration.getApplicationDirectory().exists()) { + GlobalConfiguration.getApplicationDirectory().delete(); + } + } + + @Test + public void createApplicationDirectory() { + assertTrue(GlobalConfiguration.createApplicationDirectory()); + assertTrue(GlobalConfiguration.getApplicationDirectory().exists()); + } + + @Test + public void createPluginsDirectory() { + GlobalConfiguration.createApplicationDirectory(); + assertTrue(GlobalConfiguration.createPluginsDirectory()); + assertTrue(GlobalConfiguration.getPluginsDirectory().exists()); + } + + @Test + public void createConfigurationFile() { + GlobalConfiguration.createApplicationDirectory(); + assertTrue(GlobalConfiguration.createConfigurationFile()); + assertTrue(GlobalConfiguration.getConfigurationFile().exists()); + } + + @Test + public void createLoggingConfigurationFile() { + GlobalConfiguration.createApplicationDirectory(); + assertTrue(GlobalConfiguration.createLoggingConfigurationFile()); + assertTrue(GlobalConfiguration.getLoggingConfigFile().exists()); + } +} diff --git a/slideshowfx-go-executor/build.gradle b/slideshowfx-go-executor/build.gradle new file mode 100644 index 00000000..6f4c9cce --- /dev/null +++ b/slideshowfx-go-executor/build.gradle @@ -0,0 +1,22 @@ +description = 'Snippet executor allowing to execute some go inside a SlideshowFX presentation' +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-snippet-executor') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-utils') +} + +ext.isPlugin = true +ext.isSnippetExecutor = true +ext.bundle = [ + name : 'SlideshowFX Go executor', + symbolicName : 'com.twasyl.slideshowfx.snippet.executor.go', + description : 'Allow to execute Go code inside a presentation', + activator : 'com.twasyl.slideshowfx.snippet.executor.go.activator.GoSnippetExecutorActivator', + exportPackage : 'com.twasyl.slideshowfx.snippet.executor.go,com.twasyl.slideshowfx.snippet.executor.go.activator', + setupWizardLabel: 'Go' +] \ No newline at end of file diff --git a/SlideshowFX-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutor.java b/slideshowfx-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutor.java similarity index 100% rename from SlideshowFX-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutor.java rename to slideshowfx-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutor.java diff --git a/SlideshowFX-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorOptions.java b/slideshowfx-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorOptions.java similarity index 100% rename from SlideshowFX-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorOptions.java rename to slideshowfx-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorOptions.java diff --git a/SlideshowFX-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/activator/GoSnippetExecutorActivator.java b/slideshowfx-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/activator/GoSnippetExecutorActivator.java similarity index 100% rename from SlideshowFX-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/activator/GoSnippetExecutorActivator.java rename to slideshowfx-go-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/go/activator/GoSnippetExecutorActivator.java diff --git a/SlideshowFX-go-executor/src/main/resources/META-INF/icon.png b/slideshowfx-go-executor/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-go-executor/src/main/resources/META-INF/icon.png rename to slideshowfx-go-executor/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-go-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorTest.java b/slideshowfx-go-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorTest.java similarity index 83% rename from SlideshowFX-go-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorTest.java rename to slideshowfx-go-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorTest.java index 067c769f..4f1dab8f 100644 --- a/SlideshowFX-go-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorTest.java +++ b/slideshowfx-go-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/go/GoSnippetExecutorTest.java @@ -1,13 +1,13 @@ package com.twasyl.slideshowfx.snippet.executor.go; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; -import com.twasyl.slideshowfx.utils.ResourceHelper; -import org.junit.Test; +import com.twasyl.slideshowfx.utils.io.IOUtils; +import org.junit.jupiter.api.Test; import java.io.IOException; import static com.twasyl.slideshowfx.snippet.executor.go.GoSnippetExecutor.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the class {@link GoSnippetExecutor}. @@ -111,7 +111,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageNa final CodeSnippet snippet = new CodeSnippet(); snippet.setCode("func main() {\n\tfmt.Printf(\"Hello, world.\\n\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageName_expected.txt"); + final String expected = IOUtils.read(GoSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageName_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -122,7 +122,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMain() throws IOExcepti snippet.setCode("func main() {\n\tfmt.Printf(\"Hello, world.\\n\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(GoSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -134,7 +134,7 @@ public void buildSourceCodeWithoutWrapInMain() throws IOException { snippet.setCode("func main() {\n\tfmt.Printf(\"Hello, world.\\n\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(GoSnippetExecutor.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -147,7 +147,7 @@ public void buildSourceCode() throws IOException { snippet.setCode("fmt.Printf(\"Hello, world.\\n\")"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCode_expected.txt"); + final String expected = IOUtils.read(GoSnippetExecutor.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCode_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } } diff --git a/SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageName_expected.txt b/slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageName_expected.txt similarity index 100% rename from SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageName_expected.txt rename to slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackageName_expected.txt diff --git a/SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt b/slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt rename to slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutWrapInMain_expected.txt b/slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutWrapInMain_expected.txt rename to slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCodeWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCode_expected.txt b/slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCode_expected.txt similarity index 100% rename from SlideshowFX-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCode_expected.txt rename to slideshowfx-go-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/go/buildSourceCode_expected.txt diff --git a/slideshowfx-golo-executor/build.gradle b/slideshowfx-golo-executor/build.gradle new file mode 100644 index 00000000..207ba099 --- /dev/null +++ b/slideshowfx-golo-executor/build.gradle @@ -0,0 +1,22 @@ +description = 'Snippet executor allowing to execute some Golo inside a SlideshowFX presentation' +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-snippet-executor') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-utils') +} + +ext.isPlugin = true +ext.isSnippetExecutor = true +ext.bundle = [ + name : 'SlideshowFX golo executor', + symbolicName : 'com.twasyl.slideshowfx.snippet.executor.golo', + description : 'Allow to execute Golo code inside a presentation', + activator : 'com.twasyl.slideshowfx.snippet.executor.golo.activator.GoloSnippetExecutorActivator', + exportPackage : 'com.twasyl.slideshowfx.snippet.executor.golo,com.twasyl.slideshowfx.snippet.executor.golo.activator', + setupWizardLabel: 'Golo' +] \ No newline at end of file diff --git a/SlideshowFX-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutor.java b/slideshowfx-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutor.java similarity index 100% rename from SlideshowFX-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutor.java rename to slideshowfx-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutor.java diff --git a/SlideshowFX-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorOptions.java b/slideshowfx-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorOptions.java similarity index 100% rename from SlideshowFX-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorOptions.java rename to slideshowfx-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorOptions.java diff --git a/SlideshowFX-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/activator/GoloSnippetExecutorActivator.java b/slideshowfx-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/activator/GoloSnippetExecutorActivator.java similarity index 100% rename from SlideshowFX-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/activator/GoloSnippetExecutorActivator.java rename to slideshowfx-golo-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/golo/activator/GoloSnippetExecutorActivator.java diff --git a/SlideshowFX-golo-executor/src/main/resources/META-INF/icon.png b/slideshowfx-golo-executor/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-golo-executor/src/main/resources/META-INF/icon.png rename to slideshowfx-golo-executor/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-golo-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorTest.java b/slideshowfx-golo-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorTest.java similarity index 83% rename from SlideshowFX-golo-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorTest.java rename to slideshowfx-golo-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorTest.java index 46a09615..7f7e9eed 100644 --- a/SlideshowFX-golo-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorTest.java +++ b/slideshowfx-golo-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/golo/GoloSnippetExecutorTest.java @@ -1,13 +1,13 @@ package com.twasyl.slideshowfx.snippet.executor.golo; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; -import com.twasyl.slideshowfx.utils.ResourceHelper; -import org.junit.Test; +import com.twasyl.slideshowfx.utils.io.IOUtils; +import org.junit.jupiter.api.Test; import java.io.IOException; import static com.twasyl.slideshowfx.snippet.executor.golo.GoloSnippetExecutor.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the class {@link GoloSnippetExecutor}. @@ -111,7 +111,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleNam final CodeSnippet snippet = new CodeSnippet(); snippet.setCode("function main = |args| {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleName_expected.txt"); + final String expected = IOUtils.read(GoloSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleName_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -122,7 +122,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMain() throws IOExcepti snippet.setCode("function main = |args| {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(GoloSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -134,7 +134,7 @@ public void buildSourceCodeWithoutWrapInMain() throws IOException { snippet.setCode("function main = |args| {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(GoloSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -147,7 +147,7 @@ public void buildSourceCode() throws IOException { snippet.setCode("println(\"Hello\")"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCode_expected.txt"); + final String expected = IOUtils.read(GoloSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCode_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } } diff --git a/SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleName_expected.txt b/slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleName_expected.txt similarity index 100% rename from SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleName_expected.txt rename to slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutModuleName_expected.txt diff --git a/SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt b/slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt rename to slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutWrapInMain_expected.txt b/slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutWrapInMain_expected.txt rename to slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCodeWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCode_expected.txt b/slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCode_expected.txt similarity index 100% rename from SlideshowFX-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCode_expected.txt rename to slideshowfx-golo-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/golo/buildSourceCode_expected.txt diff --git a/slideshowfx-groovy-executor/build.gradle b/slideshowfx-groovy-executor/build.gradle new file mode 100644 index 00000000..9eed89f6 --- /dev/null +++ b/slideshowfx-groovy-executor/build.gradle @@ -0,0 +1,22 @@ +description = 'Snippet executor allowing to execute some Groovy inside a SlideshowFX presentation' +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-snippet-executor') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-utils') +} + +ext.isPlugin = true +ext.isSnippetExecutor = true +ext.bundle = [ + name : 'SlideshowFX Groovy executor', + symbolicName : 'com.twasyl.slideshowfx.snippet.executor.groovy', + description : 'Allow to execute Groovy code inside a presentation', + activator : 'com.twasyl.slideshowfx.snippet.executor.groovy.activator.GroovySnippetExecutorActivator', + exportPackage : 'com.twasyl.slideshowfx.snippet.executor.groovy,com.twasyl.slideshowfx.snippet.executor.groovy.activator', + setupWizardLabel: 'Groovy' +] \ No newline at end of file diff --git a/SlideshowFX-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutor.java b/slideshowfx-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutor.java similarity index 100% rename from SlideshowFX-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutor.java rename to slideshowfx-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutor.java diff --git a/SlideshowFX-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorOptions.java b/slideshowfx-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorOptions.java similarity index 100% rename from SlideshowFX-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorOptions.java rename to slideshowfx-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorOptions.java diff --git a/SlideshowFX-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/activator/GroovySnippetExecutorActivator.java b/slideshowfx-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/activator/GroovySnippetExecutorActivator.java similarity index 100% rename from SlideshowFX-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/activator/GroovySnippetExecutorActivator.java rename to slideshowfx-groovy-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/groovy/activator/GroovySnippetExecutorActivator.java diff --git a/SlideshowFX-groovy-executor/src/main/resources/META-INF/icon.png b/slideshowfx-groovy-executor/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-groovy-executor/src/main/resources/META-INF/icon.png rename to slideshowfx-groovy-executor/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-groovy-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorTest.java b/slideshowfx-groovy-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorTest.java similarity index 83% rename from SlideshowFX-groovy-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorTest.java rename to slideshowfx-groovy-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorTest.java index f0e17700..5b346fbb 100644 --- a/SlideshowFX-groovy-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorTest.java +++ b/slideshowfx-groovy-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/groovy/GroovySnippetExecutorTest.java @@ -1,13 +1,13 @@ package com.twasyl.slideshowfx.snippet.executor.groovy; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; -import com.twasyl.slideshowfx.utils.ResourceHelper; -import org.junit.Test; +import com.twasyl.slideshowfx.utils.io.IOUtils; +import org.junit.jupiter.api.Test; import java.io.IOException; import static com.twasyl.slideshowfx.snippet.executor.groovy.GroovySnippetExecutor.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the class {@link GroovySnippetExecutor}. @@ -139,7 +139,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutC final CodeSnippet snippet = new CodeSnippet(); snippet.setCode("def static main(String ... args) {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutClassName_expected.txt"); + final String expected = IOUtils.read(GroovySnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutClassName_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -150,7 +150,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner() throws I snippet.setCode("def static main(String ... args) {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner_expected.txt"); + final String expected = IOUtils.read(GroovySnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -162,7 +162,7 @@ public void buildSourceCodeWithoutWrapInMethodRunner() throws IOException { snippet.setCode("def static main(String ... args) {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunner_expected.txt"); + final String expected = IOUtils.read(GroovySnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunner_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -175,7 +175,7 @@ public void buildSourceCodeWithoutWrapInMethodRunnerButMakeScript() throws IOExc snippet.setCode("def run() {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunnerButMakeScript_expected.txt"); + final String expected = IOUtils.read(GroovySnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunnerButMakeScript_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -188,7 +188,7 @@ public void buildSourceCode() throws IOException { snippet.setCode("println(\"Hello\")"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCode_expected.txt"); + final String expected = IOUtils.read(GroovySnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCode_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -202,7 +202,7 @@ public void buildSourceCodeAsScript() throws IOException { snippet.setCode("println(\"Hello\")"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeAsScript_expected.txt"); + final String expected = IOUtils.read(GroovySnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeAsScript_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } } diff --git a/SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeAsScript_expected.txt b/slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeAsScript_expected.txt similarity index 100% rename from SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeAsScript_expected.txt rename to slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeAsScript_expected.txt diff --git a/SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutClassName_expected.txt b/slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutClassName_expected.txt similarity index 100% rename from SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutClassName_expected.txt rename to slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunnerAndWithoutClassName_expected.txt diff --git a/SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner_expected.txt b/slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner_expected.txt similarity index 100% rename from SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner_expected.txt rename to slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutImportsAndWithoutWrapInMethodRunner_expected.txt diff --git a/SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunnerButMakeScript_expected.txt b/slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunnerButMakeScript_expected.txt similarity index 100% rename from SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunnerButMakeScript_expected.txt rename to slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunnerButMakeScript_expected.txt diff --git a/SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunner_expected.txt b/slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunner_expected.txt similarity index 100% rename from SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunner_expected.txt rename to slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCodeWithoutWrapInMethodRunner_expected.txt diff --git a/SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCode_expected.txt b/slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCode_expected.txt similarity index 100% rename from SlideshowFX-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCode_expected.txt rename to slideshowfx-groovy-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/groovy/buildSourceCode_expected.txt diff --git a/slideshowfx-hosting-connector/build.gradle b/slideshowfx-hosting-connector/build.gradle new file mode 100644 index 00000000..de7b2cbe --- /dev/null +++ b/slideshowfx-hosting-connector/build.gradle @@ -0,0 +1,10 @@ +description = 'Module defining a hosting connector allowing to connect to remote cloud service' +version = '1.1' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-engines') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-utils') +} \ No newline at end of file diff --git a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/AbstractHostingConnector.java b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/AbstractHostingConnector.java similarity index 100% rename from SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/AbstractHostingConnector.java rename to slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/AbstractHostingConnector.java diff --git a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/BasicHostingConnectorOptions.java b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/BasicHostingConnectorOptions.java similarity index 100% rename from SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/BasicHostingConnectorOptions.java rename to slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/BasicHostingConnectorOptions.java diff --git a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnector.java b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnector.java old mode 100644 new mode 100755 similarity index 65% rename from SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnector.java rename to slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnector.java index 0555f7d0..a7004727 --- a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnector.java +++ b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnector.java @@ -14,37 +14,42 @@ * This interface defines an hosting connector that will enable to connect and interact with a file hosting provider. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public interface IHostingConnector extends IPlugin, IConfigurable { /** * Returns the code that uniquely identifies this connector. The code must not contain spaces. + * * @return The code of this connector. */ String getCode(); /** * Indicates if the user is authenticated to the cloud based platform. + * * @return {@code true} if the user is authenticated, {@code false} otherwise. */ boolean isAuthenticated(); /** * Authenticate the user to the cloud based platform. + * * @throws HostingConnectorException If the authentication process fails. */ void authenticate() throws HostingConnectorException; /** * Get the access token that is get when the user is successfully logged in the service. + * * @return The access token get by the authentication process. */ String getAccessToken(); /** * Checks if the current access token is valid or not. + * * @return {@code true} is the access token is valid, {@code false} otherwise. */ boolean checkAccessToken(); @@ -57,39 +62,43 @@ public interface IHostingConnector extends I /** * Upload the given {@code engine} to the service. The presentation is uploaded to the root of the service. The * implementation should not overwrite the already existing file if any. + * * @param engine The presentation to upload. - * @throws java.lang.NullPointerException If the {@code engine} is {@code null} or if {@code engine.getArchive()} - * is {@code null}. - * @throws java.io.FileNotFoundException If the archive file does not already exist. - * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. + * @throws NullPointerException If the {@code engine} is {@code null} or if {@code engine.getArchive()} + * is {@code null}. + * @throws FileNotFoundException If the archive file does not already exist. + * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ void upload(PresentationEngine engine) throws HostingConnectorException, FileNotFoundException; /** * Upload the given {@code engine} to the service. The presentation is uploaded in the given {@code folder}. - * @param engine The presentation to upload. - * @param folder The folder where the presented will be uploaded. + * + * @param engine The presentation to upload. + * @param folder The folder where the presented will be uploaded. * @param overwrite Indicates if it should overwrite the file if it already exists. - * @throws java.lang.NullPointerException If the {@code engine} is {@code null} or if {@code engine.getArchive()} - * is {@code null}. - * @throws java.io.FileNotFoundException If the archive file does not already exist. - * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. + * @throws NullPointerException If the {@code engine} is {@code null} or if {@code engine.getArchive()} + * is {@code null}. + * @throws FileNotFoundException If the archive file does not already exist. + * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ void upload(PresentationEngine engine, RemoteFile folder, boolean overwrite) throws HostingConnectorException, FileNotFoundException; /** * Download a presentation located by {@code file} in the {@code destination} folder. + * * @param destination The folder where the presentation should be saved. - * @param file The presentation to download + * @param file The presentation to download * @return The File object representing the downloaded presentation. - * @throws java.lang.NullPointerException If either {@code destination} or {@code file} is null - * @throws java.lang.IllegalArgumentException If {@code destination} is not a folder. - * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. + * @throws NullPointerException If either {@code destination} or {@code file} is null + * @throws IllegalArgumentException If {@code destination} is not a folder. + * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ File download(File destination, RemoteFile file) throws HostingConnectorException; /** * Returns the root folder of the service. + * * @return The root folder of the service. * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ @@ -99,12 +108,13 @@ public interface IHostingConnector extends I * List all content already present in the {@code parent} directory. * If {@code includeFolders == true}, folders will be included in the final list. * If {@code includePresentations == true}, presentation files will be included in the final list. - * @param parent The folder to list the content. - * @param includeFolders {@code true} to list the folders in {@code parent} + * + * @param parent The folder to list the content. + * @param includeFolders {@code true} to list the folders in {@code parent} * @param includePresentations {@code true} to list the presentations in {@code parent} * @return The list of all content present remotely in the parent. - * @throws java.lang.NullPointerException If {@code parent} is null. - * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. + * @throws NullPointerException If {@code parent} is null. + * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ List list(RemoteFile parent, boolean includeFolders, boolean includePresentations) throws HostingConnectorException; @@ -112,12 +122,12 @@ public interface IHostingConnector extends I * Shows a dialog allowing the user to choose a file available on the hosting service. * The root folder is obtain ed by {@link IHostingConnector#getRootFolder()}. * Each time a folder is opened in the dialog, this method calls - * {@link IHostingConnector#list(com.twasyl.slideshowfx.hosting.connector.io.RemoteFile, boolean, boolean)} + * {@link IHostingConnector#list(RemoteFile, boolean, boolean)} * with the opened file as parent and {@code showFolders} and {@code showFiles} as arguments. * If the dialog is cancelled or if no selection is done, {@code null} will be returned. * * @param showFolders {@code true} for showing the folders. - * @param showFiles {@code true} for showing the files. + * @param showFiles {@code true} for showing the files. * @return The selected destination or {@code null} if the dialog is cancelled or if no selection is performed. * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ @@ -125,13 +135,23 @@ public interface IHostingConnector extends I /** * Tests if the file of the {@code engine} exists in the {@code destination} folder present remotely. - * @param engine The presentation to test the existence remotely. + * + * @param engine The presentation to test the existence remotely. * @param destination The folder where the presentation should be uploaded. The test will be performed in this folder. * @return {@code true} if the file already exists in the {@code destination} folder, {@code false otherwise}. - * - * @throws java.lang.NullPointerException If either {@code engine}, - * {@link com.twasyl.slideshowfx.engine.presentation.PresentationEngine#getArchive()} or {@code destination} is null. - * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. + * @throws NullPointerException If either {@code engine}, + * {@link PresentationEngine#getArchive()} or {@code destination} is null. + * @throws HostingConnectorException If something went wrong due to configuration, authentication or an unknown error. */ boolean fileExists(PresentationEngine engine, RemoteFile destination) throws HostingConnectorException; + + /** + * Gets the {@link RemoteFile remote file} stored in the {@code destination} folder present remotely. + * + * @param engine The presentation to get remotely. + * @param destination The folder that should contain the file. + * @return The remote file if it is found, {@code null} if not found. + * @throws HostingConnectorException If something went wrong during the retrieval. + */ + RemoteFile getRemoteFile(final PresentationEngine engine, final RemoteFile destination) throws HostingConnectorException; } diff --git a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnectorOptions.java b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnectorOptions.java similarity index 100% rename from SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnectorOptions.java rename to slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/IHostingConnectorOptions.java diff --git a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/exceptions/HostingConnectorException.java b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/exceptions/HostingConnectorException.java old mode 100644 new mode 100755 similarity index 90% rename from SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/exceptions/HostingConnectorException.java rename to slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/exceptions/HostingConnectorException.java index 0763c998..75bf022c --- a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/exceptions/HostingConnectorException.java +++ b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/exceptions/HostingConnectorException.java @@ -7,7 +7,7 @@ * error codes to identify the error. * * @author Thierry Wasylczenko - * @version 1.0.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class HostingConnectorException extends Exception { @@ -15,6 +15,7 @@ public class HostingConnectorException extends Exception { public static final short NOT_AUTHENTICATED = 2; public static final short AUTHENTICATION_FAILURE = 3; public static final short MISSING_CONFIGURATION = 4; + public static final short DESTINATION_NOT_FOUND = 5; private final short errorCode; @@ -52,6 +53,9 @@ public Pair getTitleAndMessage() { case MISSING_CONFIGURATION: pair.setKey("Missing configuration"); pair.setValue("A configuration is missing in order to interact with the service"); + case DESTINATION_NOT_FOUND: + pair.setKey("Destination not found"); + pair.setValue("The remote destination is not found"); case UNKNOWN_ERROR: default: pair.setKey("Unknown error"); diff --git a/SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFile.java b/slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFile.java similarity index 100% rename from SlideshowFX-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFile.java rename to slideshowfx-hosting-connector/src/main/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFile.java diff --git a/SlideshowFX-hosting-connector/src/test/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFileTest.java b/slideshowfx-hosting-connector/src/test/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFileTest.java similarity index 96% rename from SlideshowFX-hosting-connector/src/test/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFileTest.java rename to slideshowfx-hosting-connector/src/test/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFileTest.java index 5a56a2e1..89c1e24c 100644 --- a/SlideshowFX-hosting-connector/src/test/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFileTest.java +++ b/slideshowfx-hosting-connector/src/test/java/com/twasyl/slideshowfx/hosting/connector/io/RemoteFileTest.java @@ -1,8 +1,8 @@ package com.twasyl.slideshowfx.hosting.connector.io; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Performs test on the {@link RemoteFile} class. diff --git a/slideshowfx-html/build.gradle b/slideshowfx-html/build.gradle new file mode 100644 index 00000000..3898d331 --- /dev/null +++ b/slideshowfx-html/build.gradle @@ -0,0 +1,19 @@ +description = "Extension allowing to define slide's content using the HTML syntax" +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-markup') +} + +ext.isPlugin = true +ext.isMarkupPlugin = true +ext.bundle = [ + name : 'SlideshowFX HTML support', + symbolicName : 'com.twasyl.slideshowfx.markup.html', + description : 'Support HTML for defining slide\'s content', + activator : 'com.twasyl.slideshowfx.markup.html.activator.HtmlActivator', + exportPackage : 'com.twasyl.slideshowfx.markup.html,com.twasyl.slideshowfx.markup.html.activator', + setupWizardLabel: 'HTML' +] \ No newline at end of file diff --git a/SlideshowFX-html/src/main/java/com/twasyl/slideshowfx/markup/html/HtmlMarkup.java b/slideshowfx-html/src/main/java/com/twasyl/slideshowfx/markup/html/HtmlMarkup.java similarity index 100% rename from SlideshowFX-html/src/main/java/com/twasyl/slideshowfx/markup/html/HtmlMarkup.java rename to slideshowfx-html/src/main/java/com/twasyl/slideshowfx/markup/html/HtmlMarkup.java diff --git a/SlideshowFX-html/src/main/java/com/twasyl/slideshowfx/markup/html/activator/HtmlActivator.java b/slideshowfx-html/src/main/java/com/twasyl/slideshowfx/markup/html/activator/HtmlActivator.java similarity index 100% rename from SlideshowFX-html/src/main/java/com/twasyl/slideshowfx/markup/html/activator/HtmlActivator.java rename to slideshowfx-html/src/main/java/com/twasyl/slideshowfx/markup/html/activator/HtmlActivator.java diff --git a/SlideshowFX-html/src/main/resources/META-INF/icon.png b/slideshowfx-html/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-html/src/main/resources/META-INF/icon.png rename to slideshowfx-html/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-html/src/test/java/com/twasyl/slideshowfx/markup/html/HtmlMarkupTest.java b/slideshowfx-html/src/test/java/com/twasyl/slideshowfx/markup/html/HtmlMarkupTest.java similarity index 67% rename from SlideshowFX-html/src/test/java/com/twasyl/slideshowfx/markup/html/HtmlMarkupTest.java rename to slideshowfx-html/src/test/java/com/twasyl/slideshowfx/markup/html/HtmlMarkupTest.java index 4c579bc6..c468941b 100644 --- a/SlideshowFX-html/src/test/java/com/twasyl/slideshowfx/markup/html/HtmlMarkupTest.java +++ b/slideshowfx-html/src/test/java/com/twasyl/slideshowfx/markup/html/HtmlMarkupTest.java @@ -1,9 +1,11 @@ package com.twasyl.slideshowfx.markup.html; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Thierry Wasylczenko @@ -12,47 +14,53 @@ public class HtmlMarkupTest { private static HtmlMarkup markup; - @BeforeClass + @BeforeAll public static void setUp() { markup = new HtmlMarkup(); } - @Test(expected = IllegalArgumentException.class) + @Test public void generateWithNull() { - markup.convertAsHtml(null); + assertThrows(IllegalArgumentException.class, () -> markup.convertAsHtml(null)); } - @Test public void generateH1() { + @Test + public void generateH1() { final String result = markup.convertAsHtml("

A title

"); assertEquals("

A title

", result); } - @Test public void generateH2() { + @Test + public void generateH2() { final String result = markup.convertAsHtml("

A title

"); assertEquals("

A title

", result); } - @Test public void generateInlineCode() { + @Test + public void generateInlineCode() { final String result = markup.convertAsHtml("public class Java { }"); assertEquals("public class Java { }", result); } - @Test public void generateCodeBloc() { + @Test + public void generateCodeBloc() { final String result = markup.convertAsHtml("
final String s;
"); assertEquals("
final String s;
", result); } - @Test public void generateStrong() { + @Test + public void generateStrong() { final String result = markup.convertAsHtml("Strong text"); assertEquals("Strong text", result); } - @Test public void generateUnorderedList() { + @Test + public void generateUnorderedList() { final String result = markup.convertAsHtml("
  • One
  • Two
"); assertEquals("
  • One
  • Two
", result); diff --git a/slideshowfx-icons/build.gradle b/slideshowfx-icons/build.gradle new file mode 100755 index 00000000..04963cb8 --- /dev/null +++ b/slideshowfx-icons/build.gradle @@ -0,0 +1,4 @@ +description = 'Module allowing to insert icons in the UI' +version = '1.0' + +apply plugin: 'java-library' \ No newline at end of file diff --git a/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/FontAwesome.java b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/FontAwesome.java new file mode 100755 index 00000000..692cfc04 --- /dev/null +++ b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/FontAwesome.java @@ -0,0 +1,218 @@ +package com.twasyl.slideshowfx.icons; + +import javafx.beans.property.*; +import javafx.scene.text.Font; +import javafx.scene.text.Text; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Logger; + +/** + * Class defining FontAwesome icons. + * + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 2.0 + */ +public class FontAwesome extends Text { + private static class FontCacheKey { + private double size; + private FontType type; + + public FontCacheKey(double size, FontType type) { + this.size = size; + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FontCacheKey that = (FontCacheKey) o; + return Double.compare(that.size, size) == 0 && + type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(size, type); + } + } + + private static final Logger LOGGER = Logger.getLogger(FontAwesome.class.getName()); + private static final String FONTAWESOME_VERSION = "5.0.8"; + private static final String FONTAWESOME_ROOT = "/com/twasyl/slideshowfx/icons/fontawesome/" + FONTAWESOME_VERSION + "/"; + private static final Map FONT_CACHE = new HashMap<>(); + + private final ObjectProperty icon = new SimpleObjectProperty<>(Icon.FOLDER_OPEN); + private final DoubleProperty size = new SimpleDoubleProperty(10d); + private final StringProperty color = new SimpleStringProperty("white"); + + public FontAwesome() { + setText(getIcon().getUnicode()); + this.setFont(getFontAwesomeFont(getIcon(), 10d)); + this.definePropertyListeners(); + recomputeStyle(); + } + + public FontAwesome(final Icon icon) { + this(); + setIcon(icon); + } + + public FontAwesome(final Icon icon, final Double size) { + this(icon); + this.setSize(size); + } + + protected Font getFontAwesomeFont(final Icon icon, final double size) { + final Font font; + + final FontCacheKey key = new FontCacheKey(size, icon.getType()); + synchronized (FONT_CACHE) { + if (FONT_CACHE.containsKey(key)) { + LOGGER.fine("Returned cached font for size " + size); + font = FONT_CACHE.get(key); + } else { + LOGGER.fine("Font not found in cache for size " + size); + try (final InputStream stream = FontAwesome.getFontAwesomeFontFile(icon.getType()).openStream()) { + font = Font.loadFont(stream, size); + FONT_CACHE.put(key, font); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + + return font; + } + + protected void definePropertyListeners() { + this.icon.addListener((value, oldIcon, newIcon) -> { + this.setFont(getFontAwesomeFont(newIcon, getSize())); + setText(newIcon.getUnicode()); + recomputeStyle(); + }); + + this.size.addListener((value, oldSize, newSize) -> { + this.setFont(getFontAwesomeFont(getIcon(), newSize.doubleValue())); + recomputeStyle(); + }); + + this.color.addListener((value, oldSize, newSize) -> recomputeStyle()); + } + + public ObjectProperty iconProperty() { + return icon; + } + + public Icon getIcon() { + return icon.get(); + } + + public void setIcon(Icon icon) { + this.icon.set(icon); + } + + public DoubleProperty sizeProperty() { + return size; + } + + public Double getSize() { + return size.get(); + } + + public void setSize(Double size) { + this.size.set(size); + } + + public StringProperty colorProperty() { + return color; + } + + public String getColor() { + return color.get(); + } + + public void setColor(String color) { + this.color.set(color); + } + + protected void recomputeStyle() { + setStyle(String.format("-fx-fill: %s;", getColor())); + } + + /** + * Get the FontAwesome version provided by SlideshowFX. + * + * @return The version of FontAwesome. + */ + public static String getFontAwesomeVersion() { + return FONTAWESOME_VERSION; + } + + /** + * Get the font file for the desired type. + * + * @param type The type of the desired font. + * @return The {@link URL} of the font file. + */ + public static URL getFontAwesomeFontFile(final FontType type) { + final StringBuilder path = new StringBuilder("fonts/fontawesome-") + .append(type.name().toLowerCase()).append("-").append(getFontAwesomeVersion()).append(".otf"); + + return getFontAwesomeFile(path.toString()); + } + + /** + * Get the CSS file of FontAwesome. + * + * @return The {@link InputStream} of the CSS font file. + */ + public static URL getFontAwesomeCSSFile() { + return getFontAwesomeFile("css/" + getFontAwesomeCSSFilename()); + } + + /** + * Get the name of the FontAwesome CSS file. The returned named doesn't contain any path. + * + * @return The name of the FontAwesome CSS file. + */ + public static String getFontAwesomeCSSFilename() { + return "fa-svg-with-js.css"; + } + + /** + * Get the JavaScript file of FontAwesome. + * + * @return The {@link URL} of the JavaScript font file. + */ + public static URL getFontAwesomeJSFile() { + return getFontAwesomeFile("js/" + getFontAwesomeJSFilename()); + } + + /** + * Get the name of the FontAwesome JavaScript file. The returned named doesn't contain any path. + * + * @return The name of the FontAwesome JavaScript file. + */ + public static String getFontAwesomeJSFilename() { + return "fontawesome-all.min.js"; + } + + /** + *

Get a FontAwesome file from a given relative path. The path is relative from the root package + * where all FontAwesome files are stored within the JAR.

+ * + * @param relativePath The relative path of the file to get + * @return The {@link URL} of the FontAwesome file. + */ + public static URL getFontAwesomeFile(final String relativePath) { + return FontAwesome.class.getResource(FONTAWESOME_ROOT + relativePath); + } +} diff --git a/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/FontType.java b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/FontType.java new file mode 100755 index 00000000..41233e05 --- /dev/null +++ b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/FontType.java @@ -0,0 +1,11 @@ +package com.twasyl.slideshowfx.icons; + +/** + * Represent the type of font for FontAwesome + * + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public enum FontType { + SOLID, REGULAR, BRAND +} diff --git a/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/Icon.java b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/Icon.java new file mode 100755 index 00000000..fcfd7cae --- /dev/null +++ b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/Icon.java @@ -0,0 +1,69 @@ +package com.twasyl.slideshowfx.icons; + +import static com.twasyl.slideshowfx.icons.FontType.BRAND; +import static com.twasyl.slideshowfx.icons.FontType.REGULAR; +import static com.twasyl.slideshowfx.icons.FontType.SOLID; + +/** + * Enum representing FontAwesome icons supported. + * + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public enum Icon { + ARCHIVE("\uf187", SOLID), + ARROW_ALT_CIRCLE_LEFT("\uf359", REGULAR), + ARROW_ALT_CIRCLE_RIGHT("\uf35a", REGULAR), + ARROW_RIGHT("\uf061", SOLID), + CHECK_CIRCLE("\uf058", SOLID), + CHEVRON_LEFT("\uf053", SOLID), + CHEVRON_RIGHT("\uf054", SOLID), + CODE("\uf121", SOLID), + COG("\uf013", SOLID), + COMMENTS_O("\uf086", REGULAR), + DESKTOP("\uf108", SOLID), + EXCLAMATION_CIRCLE("\uf06a", SOLID), + EXCLAMATION_TRIANGLE("\uf071", SOLID), + FILE("\uf15b", SOLID), + FILE_TEXT_ALT("\uf0f6", SOLID), + FILES("\uf0c5", SOLID), + FLOPPY("\uf0c7", SOLID), + FOLDER("\uf07b", SOLID), + FOLDER_OPEN("\uf07c", SOLID), + LINK("\uf0c1", SOLID), + REFRESH("\uf021", SOLID), + PICTURE_ALT("\uf03e", REGULAR), + PLAY("\uf04b", SOLID), + PLUS("\uf067", SOLID), + PLUS_SQUARE("\uf0fe", SOLID), + POWER_OFF("\uf011", SOLID), + PRINT("\uf02f", SOLID), + QRCODE("\uf029", SOLID), + QUESTION("\uf128", SOLID), + QUOTE_LEFT("\uf10d", SOLID), + SHARE_ALT_SQUARE("\uf1e1", SOLID), + SIGN_OUT_ALT("\uf2f5", SOLID), + SPINNER("\uf110", SOLID), + STAR("\uf005", REGULAR), + TERMINAL("\uf120", SOLID), + TIMES("\uf00d", SOLID), + TIMES_CIRCLE("\uf057", REGULAR), + TRASH_ALT("\uf2ed", REGULAR), + TWITTER("\uf099", BRAND); + + private final String unicode; + private final FontType type; + + Icon(final String unicode, final FontType fontType) { + this.unicode = unicode; + this.type = fontType; + } + + public String getUnicode() { + return unicode; + } + + public FontType getType() { + return type; + } +} diff --git a/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/IconStack.java b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/IconStack.java new file mode 100755 index 00000000..9b79a5a6 --- /dev/null +++ b/slideshowfx-icons/src/main/java/com/twasyl/slideshowfx/icons/IconStack.java @@ -0,0 +1,11 @@ +package com.twasyl.slideshowfx.icons; + +import javafx.scene.layout.StackPane; + +/** + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 2.0 + */ +public class IconStack extends StackPane { +} diff --git a/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/css/fa-svg-with-js.css b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/css/fa-svg-with-js.css new file mode 100644 index 00000000..288850ee --- /dev/null +++ b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/css/fa-svg-with-js.css @@ -0,0 +1,343 @@ +/*! + * Font Awesome Free 5.0.8 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +svg:not(:root).svg-inline--fa { + overflow: visible; } + +.svg-inline--fa { + display: inline-block; + font-size: inherit; + height: 1em; + overflow: visible; + vertical-align: -.125em; } + .svg-inline--fa.fa-lg { + vertical-align: -.225em; } + .svg-inline--fa.fa-w-1 { + width: 0.0625em; } + .svg-inline--fa.fa-w-2 { + width: 0.125em; } + .svg-inline--fa.fa-w-3 { + width: 0.1875em; } + .svg-inline--fa.fa-w-4 { + width: 0.25em; } + .svg-inline--fa.fa-w-5 { + width: 0.3125em; } + .svg-inline--fa.fa-w-6 { + width: 0.375em; } + .svg-inline--fa.fa-w-7 { + width: 0.4375em; } + .svg-inline--fa.fa-w-8 { + width: 0.5em; } + .svg-inline--fa.fa-w-9 { + width: 0.5625em; } + .svg-inline--fa.fa-w-10 { + width: 0.625em; } + .svg-inline--fa.fa-w-11 { + width: 0.6875em; } + .svg-inline--fa.fa-w-12 { + width: 0.75em; } + .svg-inline--fa.fa-w-13 { + width: 0.8125em; } + .svg-inline--fa.fa-w-14 { + width: 0.875em; } + .svg-inline--fa.fa-w-15 { + width: 0.9375em; } + .svg-inline--fa.fa-w-16 { + width: 1em; } + .svg-inline--fa.fa-w-17 { + width: 1.0625em; } + .svg-inline--fa.fa-w-18 { + width: 1.125em; } + .svg-inline--fa.fa-w-19 { + width: 1.1875em; } + .svg-inline--fa.fa-w-20 { + width: 1.25em; } + .svg-inline--fa.fa-pull-left { + margin-right: .3em; + width: auto; } + .svg-inline--fa.fa-pull-right { + margin-left: .3em; + width: auto; } + .svg-inline--fa.fa-border { + height: 1.5em; } + .svg-inline--fa.fa-li { + width: 2em; } + .svg-inline--fa.fa-fw { + width: 1.25em; } + +.fa-layers svg.svg-inline--fa { + bottom: 0; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; } + +.fa-layers { + display: inline-block; + height: 1em; + position: relative; + text-align: center; + vertical-align: -.125em; + width: 1em; } + .fa-layers svg.svg-inline--fa { + -webkit-transform-origin: center center; + transform-origin: center center; } + +.fa-layers-text, .fa-layers-counter { + display: inline-block; + position: absolute; + text-align: center; } + +.fa-layers-text { + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + -webkit-transform-origin: center center; + transform-origin: center center; } + +.fa-layers-counter { + background-color: #ff253a; + border-radius: 1em; + color: #fff; + height: 1.5em; + line-height: 1; + max-width: 5em; + min-width: 1.5em; + overflow: hidden; + padding: .25em; + right: 0; + text-overflow: ellipsis; + top: 0; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: top right; + transform-origin: top right; } + +.fa-layers-bottom-right { + bottom: 0; + right: 0; + top: auto; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: bottom right; + transform-origin: bottom right; } + +.fa-layers-bottom-left { + bottom: 0; + left: 0; + right: auto; + top: auto; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: bottom left; + transform-origin: bottom left; } + +.fa-layers-top-right { + right: 0; + top: 0; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: top right; + transform-origin: top right; } + +.fa-layers-top-left { + left: 0; + right: auto; + top: 0; + -webkit-transform: scale(0.25); + transform: scale(0.25); + -webkit-transform-origin: top left; + transform-origin: top left; } + +.fa-lg { + font-size: 1.33333em; + line-height: 0.75em; + vertical-align: -.0667em; } + +.fa-xs { + font-size: .75em; } + +.fa-sm { + font-size: .875em; } + +.fa-1x { + font-size: 1em; } + +.fa-2x { + font-size: 2em; } + +.fa-3x { + font-size: 3em; } + +.fa-4x { + font-size: 4em; } + +.fa-5x { + font-size: 5em; } + +.fa-6x { + font-size: 6em; } + +.fa-7x { + font-size: 7em; } + +.fa-8x { + font-size: 8em; } + +.fa-9x { + font-size: 9em; } + +.fa-10x { + font-size: 10em; } + +.fa-fw { + text-align: center; + width: 1.25em; } + +.fa-ul { + list-style-type: none; + margin-left: 2.5em; + padding-left: 0; } + .fa-ul > li { + position: relative; } + +.fa-li { + left: -2em; + position: absolute; + text-align: center; + width: 2em; + line-height: inherit; } + +.fa-border { + border: solid 0.08em #eee; + border-radius: .1em; + padding: .2em .25em .15em; } + +.fa-pull-left { + float: left; } + +.fa-pull-right { + float: right; } + +.fa.fa-pull-left, +.fas.fa-pull-left, +.far.fa-pull-left, +.fal.fa-pull-left, +.fab.fa-pull-left { + margin-right: .3em; } + +.fa.fa-pull-right, +.fas.fa-pull-right, +.far.fa-pull-right, +.fal.fa-pull-right, +.fab.fa-pull-right { + margin-left: .3em; } + +.fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; } + +.fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); } + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +.fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + +.fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + +.fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + +.fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + transform: scale(1, -1); } + +.fa-flip-horizontal.fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); } + +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + -webkit-filter: none; + filter: none; } + +.fa-stack { + display: inline-block; + height: 2em; + position: relative; + width: 2em; } + +.fa-stack-1x, +.fa-stack-2x { + bottom: 0; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; } + +.svg-inline--fa.fa-stack-1x { + height: 1em; + width: 1em; } + +.svg-inline--fa.fa-stack-2x { + height: 2em; + width: 2em; } + +.fa-inverse { + color: #fff; } + +.sr-only { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } + +.sr-only-focusable:active, .sr-only-focusable:focus { + clip: auto; + height: auto; + margin: 0; + overflow: visible; + position: static; + width: auto; } diff --git a/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-brand-5.0.8.otf b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-brand-5.0.8.otf new file mode 100644 index 00000000..980bd0af Binary files /dev/null and b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-brand-5.0.8.otf differ diff --git a/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-regular-5.0.8.otf b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-regular-5.0.8.otf new file mode 100644 index 00000000..5f8f4fd9 Binary files /dev/null and b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-regular-5.0.8.otf differ diff --git a/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-solid-5.0.8.otf b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-solid-5.0.8.otf new file mode 100644 index 00000000..6821a40c Binary files /dev/null and b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/fonts/fontawesome-solid-5.0.8.otf differ diff --git a/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/js/fontawesome-all.min.js b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/js/fontawesome-all.min.js new file mode 100644 index 00000000..cfce4315 --- /dev/null +++ b/slideshowfx-icons/src/main/resources/com/twasyl/slideshowfx/icons/fontawesome/5.0.8/js/fontawesome-all.min.js @@ -0,0 +1,5 @@ +/*! + * Font Awesome Free 5.0.8 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +!function(){"use strict";var c={};try{"undefined"!=typeof window&&(c=window)}catch(c){}var l=(c.navigator||{}).userAgent,h=void 0===l?"":l,v=c,z=(~h.indexOf("MSIE")||h.indexOf("Trident/"),"___FONT_AWESOME___"),e=function(){try{return!0}catch(c){return!1}}(),a=[1,2,3,4,5,6,7,8,9,10],m=a.concat([11,12,13,14,15,16,17,18,19,20]);["xs","sm","lg","fw","ul","li","border","pull-left","pull-right","spin","pulse","rotate-90","rotate-180","rotate-270","flip-horizontal","flip-vertical","stack","stack-1x","stack-2x","inverse","layers","layers-text","layers-counter"].concat(a.map(function(c){return c+"x"})).concat(m.map(function(c){return"w-"+c}));var t=v||{};t[z]||(t[z]={}),t[z].styles||(t[z].styles={}),t[z].hooks||(t[z].hooks={}),t[z].shims||(t[z].shims=[]);var s=t[z],r=Object.assign||function(c){for(var l=1;l1&&void 0!==arguments[1]?arguments[1]:{}).asNewDefault,h=void 0!==l&&l,v=Object.keys(O),z=h?function(c){return~v.indexOf(c)&&!~A.indexOf(c)}:function(c){return~v.indexOf(c)};Object.keys(c).forEach(function(l){z(l)&&(O[l]=c[l])})}m.FontAwesomeConfig=O;var N=m||{};N[n]||(N[n]={}),N[n].styles||(N[n].styles={}),N[n].hooks||(N[n].hooks={}),N[n].shims||(N[n].shims=[]);var E=N[n],P=[],_=!1;M&&((_=(t.documentElement.doScroll?/^loaded|^c/:/^loaded|^i|^c/).test(t.readyState))||t.addEventListener("DOMContentLoaded",function c(){t.removeEventListener("DOMContentLoaded",c),_=1,P.map(function(c){return c()})}));var T=function(c){M&&(_?setTimeout(c,0):P.push(c))},F=H,I={size:16,x:0,y:0,rotate:0,flipX:!1,flipY:!1};function R(c){if(c&&M){var l=t.createElement("style");l.setAttribute("type","text/css"),l.innerHTML=c;for(var h=t.head.childNodes,v=null,z=h.length-1;z>-1;z--){var e=h[z],a=(e.tagName||"").toUpperCase();["STYLE","LINK"].indexOf(a)>-1&&(v=e)}return t.head.insertBefore(l,v),c}}var W=0;function B(){return++W}function D(c){for(var l=[],h=(c||[]).length>>>0;h--;)l[h]=c[h];return l}function X(c){return c.classList?D(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function Y(c,l){var h,v=l.split("-"),z=v[0],e=v.slice(1).join("-");return z!==c||""===e||(h=e,~g.indexOf(h))?null:e}function U(c){return(""+c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function K(c){return Object.keys(c||{}).reduce(function(l,h){return l+(h+": ")+c[h]+";"},"")}function G(c){return c.size!==I.size||c.x!==I.x||c.y!==I.y||c.rotate!==I.rotate||c.flipX||c.flipY}function J(c){var l=c.transform,h=c.containerWidth,v=c.iconWidth;return{outer:{transform:"translate("+h/2+" 256)"},inner:{transform:"translate("+32*l.x+", "+32*l.y+") "+" "+("scale("+l.size/16*(l.flipX?-1:1)+", "+l.size/16*(l.flipY?-1:1)+") ")+" "+("rotate("+l.rotate+" 0 0)")},path:{transform:"translate("+v/2*-1+" -256)"}}}var Q={x:0,y:0,width:"100%",height:"100%"},Z=function(c){var l=c.children,h=c.attributes,v=c.main,z=c.mask,e=c.transform,a=v.width,m=v.icon,t=z.width,s=z.icon,r=J({transform:e,containerWidth:t,iconWidth:a}),f={tag:"rect",attributes:k({},Q,{fill:"white"})},M={tag:"g",attributes:k({},r.inner),children:[{tag:"path",attributes:k({},m.attributes,r.path,{fill:"black"})}]},i={tag:"g",attributes:k({},r.outer),children:[M]},n="mask-"+B(),H="clip-"+B(),o={tag:"defs",children:[{tag:"clipPath",attributes:{id:H},children:[s]},{tag:"mask",attributes:k({},Q,{id:n,maskUnits:"userSpaceOnUse",maskContentUnits:"userSpaceOnUse"}),children:[f,i]}]};return l.push(o,{tag:"rect",attributes:k({fill:"currentColor","clip-path":"url(#"+H+")",mask:"url(#"+n+")"},Q)}),{children:l,attributes:h}},$=function(c){var l=c.children,h=c.attributes,v=c.main,z=c.transform,e=K(c.styles);if(e.length>0&&(h.style=e),G(z)){var a=J({transform:z,containerWidth:v.width,iconWidth:v.width});l.push({tag:"g",attributes:k({},a.outer),children:[{tag:"g",attributes:k({},a.inner),children:[{tag:v.icon.tag,children:v.icon.children,attributes:k({},v.icon.attributes,a.path)}]}]})}else l.push(v.icon);return{children:l,attributes:h}},cc=function(c){var l=c.children,h=c.main,v=c.mask,z=c.attributes,e=c.styles,a=c.transform;if(G(a)&&h.found&&!v.found){var m=h.width/h.height/2,t=.5;z.style=K(k({},e,{"transform-origin":m+a.x/16+"em "+(t+a.y/16)+"em"}))}return[{tag:"svg",attributes:z,children:l}]},lc=function(c){var l=c.prefix,h=c.iconName,v=c.children,z=c.attributes,e=c.symbol,a=!0===e?l+"-"+O.familyPrefix+"-"+h:e;return[{tag:"svg",attributes:{style:"display: none;"},children:[{tag:"symbol",attributes:k({},z,{id:a}),children:v}]}]};function hc(c){var l=c.icons,h=l.main,v=l.mask,z=c.prefix,e=c.iconName,a=c.transform,m=c.symbol,t=c.title,s=c.extra,r=c.watchable,f=void 0!==r&&r,M=v.found?v:h,i=M.width,n=M.height,H="fa-w-"+Math.ceil(i/n*16),o=[O.replacementClass,e?O.familyPrefix+"-"+e:"",H].concat(s.classes).join(" "),C={children:[],attributes:k({},s.attributes,{"data-prefix":z,"data-icon":e,class:o,role:"img",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 "+i+" "+n})};f&&(C.attributes[V]=""),t&&C.children.push({tag:"title",attributes:{id:C.attributes["aria-labelledby"]||"title-"+B()},children:[t]});var L=k({},C,{prefix:z,iconName:e,main:h,mask:v,transform:a,symbol:m,styles:s.styles}),u=v.found&&h.found?Z(L):$(L),d=u.children,p=u.attributes;return L.children=d,L.attributes=p,m?lc(L):cc(L)}function vc(c){var l=c.content,h=c.width,v=c.height,z=c.transform,e=c.title,a=c.extra,m=c.watchable,t=void 0!==m&&m,s=k({},a.attributes,e?{title:e}:{},{class:a.classes.join(" ")});t&&(s[V]="");var r,f,M,n,o,C,L,u,d,p=k({},a.styles);G(z)&&(p.transform=(f=(r={transform:z,startCentered:!0,width:h,height:v}).transform,M=r.width,n=void 0===M?H:M,o=r.height,C=void 0===o?H:o,L=r.startCentered,d="",d+=(u=void 0!==L&&L)&&i?"translate("+(f.x/F-n/2)+"em, "+(f.y/F-C/2)+"em) ":u?"translate(calc(-50% + "+f.x/F+"em), calc(-50% + "+f.y/F+"em)) ":"translate("+f.x/F+"em, "+f.y/F+"em) ",d+="scale("+f.size/F*(f.flipX?-1:1)+", "+f.size/F*(f.flipY?-1:1)+") ",d+="rotate("+f.rotate+"deg) "),p["-webkit-transform"]=p.transform);var b=K(p);b.length>0&&(s.style=b);var g=[];return g.push({tag:"span",attributes:s,children:[l]}),e&&g.push({tag:"span",attributes:{class:"sr-only"},children:[e]}),g}var zc=function(){},ec=O.measurePerformance&&r&&r.mark&&r.measure?r:{mark:zc,measure:zc},ac='FA "5.0.8"',mc=function(c){ec.mark(ac+" "+c+" ends"),ec.measure(ac+" "+c,ac+" "+c+" begins",ac+" "+c+" ends")},tc={begin:function(c){return ec.mark(ac+" "+c+" begins"),function(){return mc(c)}},end:mc},sc=function(c,l,h,v){var z,e,a,m,t,s=Object.keys(c),r=s.length,f=void 0!==v?(m=l,t=v,function(c,l,h,v){return m.call(t,c,l,h,v)}):l;for(void 0===h?(z=1,a=c[s[0]]):(z=0,a=h);z"+a.map(uc).join("")+""}var dc=function(){};function pc(c){return"string"==typeof(c.getAttribute?c.getAttribute(V):null)}var bc={replace:function(c){var l=c[0],h=c[1].map(function(c){return uc(c)}).join("\n");if(l.parentNode&&l.outerHTML)l.outerHTML=h+(O.keepOriginalSource&&"svg"!==l.tagName.toLowerCase()?"\x3c!-- "+l.outerHTML+" --\x3e":"");else if(l.parentNode){var v=document.createElement("span");l.parentNode.replaceChild(v,l),v.outerHTML=h}},nest:function(c){var l=c[0],h=c[1];if(~X(l).indexOf(O.replacementClass))return bc.replace(c);var v=new RegExp(O.familyPrefix+"-.*");delete h[0].attributes.style;var z=h[0].attributes.class.split(" ").reduce(function(c,l){return l===O.replacementClass||l.match(v)?c.toSvg.push(l):c.toNode.push(l),c},{toNode:[],toSvg:[]});h[0].attributes.class=z.toSvg.join(" ");var e=h.map(function(c){return uc(c)}).join("\n");l.setAttribute("class",z.toNode.join(" ")),l.setAttribute(V,""),l.innerHTML=e}};function gc(c,l){var h="function"==typeof l?l:dc;0===c.length?h():(m.requestAnimationFrame||function(c){return c()})(function(){var l=!0===O.autoReplaceSvg?bc.replace:bc[O.autoReplaceSvg]||bc.replace,v=tc.begin("mutate");c.map(l),v(),h()})}var yc=!1;var wc=null;var kc=function(c){var l=c.getAttribute("style"),h=[];return l&&(h=l.split(";").reduce(function(c,l){var h=l.split(":"),v=h[0],z=h.slice(1);return v&&z.length>0&&(c[v]=z.join(":").trim()),c},{})),h};var Sc=function(c){var l,h,v,z,e=c.getAttribute("data-prefix"),a=c.getAttribute("data-icon"),m=void 0!==c.innerText?c.innerText.trim():"",t=Cc(X(c));return e&&a&&(t.prefix=e,t.iconName=a),t.prefix&&m.length>1?t.iconName=(v=t.prefix,z=c.innerText,ic[v][z]):t.prefix&&1===m.length&&(t.iconName=(l=t.prefix,h=function(c){for(var l="",h=0;h-1&&Yc(z.nextSibling),Yc(z),z=null),v&&!z){var e=h.getPropertyValue("content"),a=t.createElement("i");a.setAttribute("class",""+Bc[v[1]]),a.setAttribute(C,l),a.innerText=3===e.length?e.substr(1,1):e,":before"===l?c.insertBefore(a,c.firstChild):c.appendChild(a)}})})}(),yc=!1,l()}}function Kc(c){var l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(M){var h=t.documentElement.classList,v=function(c){return h.add(L+"-"+c)},z=function(c){return h.remove(L+"-"+c)},e=Object.keys(Ic),a=["."+Rc+":not(["+V+"])"].concat(e.map(function(c){return"."+c+":not(["+V+"])"})).join(", ");if(0!==a.length){var m=D(c.querySelectorAll(a));if(m.length>0){v("pending"),z("complete");var s=tc.begin("onTree"),r=m.reduce(function(c,l){try{var h=Xc(l);h&&c.push(h)}catch(c){u||c instanceof Nc&&console.error(c)}return c},[]);s(),gc(r,function(){v("active"),v("complete"),z("pending"),"function"==typeof l&&l()})}}}}function Gc(c){var l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,h=Xc(c);h&&gc([h],l)}var Jc=function(){var c=o,l=O.familyPrefix,h=O.replacementClass,v="svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;overflow:visible;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);-webkit-transform-origin:center center;transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom right;transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:bottom left;transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top right;transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;-webkit-transform:scale(.25);transform:scale(.25);-webkit-transform-origin:top left;transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:solid .08em #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);transform:scale(1,-1)}.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1,-1);transform:scale(-1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}";if("fa"!==l||h!==c){var z=new RegExp("\\.fa\\-","g"),e=new RegExp("\\."+c,"g");v=v.replace(z,"."+l+"-").replace(e,"."+h)}return v};var Qc=function(){function c(){y(this,c),this.definitions={}}return w(c,[{key:"add",value:function(){for(var c=this,l=arguments.length,h=Array(l),v=0;v1&&void 0!==arguments[1]?arguments[1]:{},h=l.transform,v=void 0===h?I:h,z=l.symbol,e=void 0!==z&&z,a=l.mask,m=void 0===a?null:a,t=l.title,s=void 0===t?null:t,r=l.classes,f=void 0===r?[]:r,M=l.attributes,i=void 0===M?{}:M,n=l.styles,H=void 0===n?{}:n;if(c){var o=c.prefix,V=c.iconName,C=c.icon;return ll(k({type:"icon"},c),function(){return cl(),O.autoA11y&&(s?i["aria-labelledby"]=O.replacementClass+"-title-"+B():i["aria-hidden"]="true"),hc({icons:{main:Zc(C),mask:m?Zc(m.icon):{found:!1,width:null,height:null,icon:{}}},prefix:o,iconName:V,transform:k({},I,v),symbol:e,title:s,extra:{attributes:i,styles:H,classes:f}})})}},function(c){var l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},h=(c||{}).icon?c:hl(c||{}),v=l.mask;return v&&(v=(v||{}).icon?v:hl(v||{})),vl(h,k({},l,{mask:v}))}),al={noAuto:function(){var c;j({autoReplaceSvg:c=!1,observeMutations:c}),wc&&wc.disconnect()},dom:{i2svg:function(){var c=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(M){cl();var l=c.node,h=void 0===l?t:l,v=c.callback,z=void 0===v?function(){}:v;O.searchPseudoElements&&Uc(h),Kc(h,z)}},css:Jc,insertCss:function(){R(Jc())}},library:zl,parse:{transform:function(c){return xc(c)}},findIconDefinition:hl,icon:el,text:function(c){var l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},h=l.transform,v=void 0===h?I:h,z=l.title,e=void 0===z?null:z,a=l.classes,m=void 0===a?[]:a,t=l.attributes,s=void 0===t?{}:t,r=l.styles,f=void 0===r?{}:r;return ll({type:"text",content:c},function(){return cl(),vc({content:c,transform:k({},I,v),title:e,extra:{attributes:s,styles:f,classes:[O.familyPrefix+"-layers-text"].concat(S(m))}})})},layer:function(c){return ll({type:"layer"},function(){cl();var l=[];return c(function(c){Array.isArray(c)?c.map(function(c){l=l.concat(c.abstract)}):l=l.concat(c.abstract)}),[{tag:"span",attributes:{class:O.familyPrefix+"-layers"},children:l}]})}},ml=function(){M&&O.autoReplaceSvg&&al.dom.i2svg({node:t})};Object.defineProperty(al,"config",{get:function(){return O},set:function(c){j(c)}}),function(c){try{c()}catch(c){if(!u)throw c}}(function(){f&&(m.FontAwesome||(m.FontAwesome=al),T(function(){Object.keys(E.styles).length>0&&ml(),O.observeMutations&&"function"==typeof MutationObserver&&function(c){if(s){var l=c.treeCallback,h=c.nodeCallback,v=c.pseudoElementsCallback;wc=new s(function(c){yc||D(c).forEach(function(c){if("childList"===c.type&&c.addedNodes.length>0&&!pc(c.addedNodes[0])&&(O.searchPseudoElements&&v(c.target),l(c.target)),"attributes"===c.type&&c.target.parentNode&&O.searchPseudoElements&&v(c.target.parentNode),"attributes"===c.type&&pc(c.target)&&~b.indexOf(c.attributeName))if("class"===c.attributeName){var z=Cc(X(c.target)),e=z.prefix,a=z.iconName;e&&c.target.setAttribute("data-prefix",e),a&&c.target.setAttribute("data-icon",a)}else h(c.target)})}),M&&wc.observe(t.getElementsByTagName("body")[0],{childList:!0,attributes:!0,characterData:!0,subtree:!0})}}({treeCallback:Kc,nodeCallback:Gc,pseudoElementsCallback:Uc})})),E.hooks=k({},E.hooks,{addPack:function(c,l){E.styles[c]=k({},E.styles[c]||{},l),Hc(),ml()},addShims:function(c){var l;(l=E.shims).push.apply(l,S(c)),Hc(),ml()}})})}(); \ No newline at end of file diff --git a/slideshowfx-icons/src/test/java/com/twasyl/slideshowfx/icons/FontAwesomeTest.java b/slideshowfx-icons/src/test/java/com/twasyl/slideshowfx/icons/FontAwesomeTest.java new file mode 100755 index 00000000..cada923e --- /dev/null +++ b/slideshowfx-icons/src/test/java/com/twasyl/slideshowfx/icons/FontAwesomeTest.java @@ -0,0 +1,61 @@ +package com.twasyl.slideshowfx.icons; + +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + +import static com.twasyl.slideshowfx.icons.FontType.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test of class {@link FontAwesome}. + * + * @author Thierry Wasylczenko + * @since SlideshowFX 2.0 + */ +public class FontAwesomeTest { + + protected void assertFontAwesomeUrl(final URL url) throws URISyntaxException { + assertNotNull(url); + final File file = new File(url.toURI()); + assertTrue(file.exists(), "URL doesn't exist: " + url.toExternalForm()); + } + + @Test + public void obtainRegularFontFile() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeFontFile(REGULAR)); + } + + @Test + public void obtainSolidFontFile() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeFontFile(SOLID)); + } + + @Test + public void obtainBrandFontFile() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeFontFile(BRAND)); + } + + @Test + public void obtainCSSFontFile() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeCSSFile()); + } + + @Test + public void obtainJavaScriptFontFile() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeJSFile()); + } + + @Test + public void obtainJavaScriptFileFromRelativePath() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeFile("js/fontawesome-all.min.js")); + } + + @Test + public void obtainCSSFileFromRelativePath() throws URISyntaxException { + assertFontAwesomeUrl(FontAwesome.getFontAwesomeFile("css/fa-svg-with-js.css")); + } +} diff --git a/slideshowfx-image-extension/build.gradle b/slideshowfx-image-extension/build.gradle new file mode 100755 index 00000000..a53399df --- /dev/null +++ b/slideshowfx-image-extension/build.gradle @@ -0,0 +1,25 @@ +description = 'Extension allowing to insert images inside a SlideshowFX presentation' +version = '1.3' + +apply plugin: 'java-library' + +dependencies { + api project(':slideshowfx-content-extension') + implementation project(':slideshowfx-icons') + api project(':slideshowfx-markup') + api project(':slideshowfx-plugin') + implementation project(':slideshowfx-osgi') + api project(':slideshowfx-ui-controls') +} + +ext.isPlugin = true +ext.isContentExtension = true +ext.bundle = [ + name : 'SlideshowFX image extension', + symbolicName : 'com.twasyl.slideshowfx.content.extension.image', + description : 'Support for inserting image in slides', + activator : 'com.twasyl.slideshowfx.content.extension.image.activator.ImageContentExtensionActivator', + exportPackage : 'com.twasyl.slideshowfx.content.extension.image.controllers,com.twasyl.slideshowfx.content.extension.image,com.twasyl.slideshowfx.content.extension.image.activator', + setupWizardLabel : 'Image', + setupWizardIconName: 'PICTURE_ALT' +] \ No newline at end of file diff --git a/SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/ImageContentExtension.java b/slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/ImageContentExtension.java similarity index 90% rename from SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/ImageContentExtension.java rename to slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/ImageContentExtension.java index 089e2e7b..d801fe68 100755 --- a/SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/ImageContentExtension.java +++ b/slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/ImageContentExtension.java @@ -3,7 +3,7 @@ import com.twasyl.slideshowfx.content.extension.AbstractContentExtension; import com.twasyl.slideshowfx.content.extension.image.controllers.ImageContentExtensionController; import com.twasyl.slideshowfx.markup.IMarkup; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.fxml.FXMLLoader; import javafx.scene.layout.Pane; @@ -11,13 +11,15 @@ import java.util.logging.Level; import java.util.logging.Logger; +import static com.twasyl.slideshowfx.icons.Icon.PICTURE_ALT; + /** * The ImageContentExtension extends the AbstractContentExtension. It allows to build a content containing images to insert * inside a SlideshowFX presentation. * This extension supports HTML and Textile markup languages. * * @author Thierry Wasylczenko - * @version 1.2 + * @version 1.3 * @since SlideshowFX 1.0 */ public class ImageContentExtension extends AbstractContentExtension { @@ -27,7 +29,7 @@ public class ImageContentExtension extends AbstractContentExtension { public ImageContentExtension() { super("IMAGE", null, - FontAwesomeIcon.PICTURE_ALT, + PICTURE_ALT, "Insert an image", "Insert an image"); } @@ -77,4 +79,9 @@ public String buildDefaultContentString() { return builder.toString(); } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + return this.controller.areInputsValid(); + } } diff --git a/SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/activator/ImageContentExtensionActivator.java b/slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/activator/ImageContentExtensionActivator.java similarity index 100% rename from SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/activator/ImageContentExtensionActivator.java rename to slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/activator/ImageContentExtensionActivator.java diff --git a/SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/controllers/ImageContentExtensionController.java b/slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/controllers/ImageContentExtensionController.java similarity index 93% rename from SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/controllers/ImageContentExtensionController.java rename to slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/controllers/ImageContentExtensionController.java index f37940bc..90fea75c 100755 --- a/SlideshowFX-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/controllers/ImageContentExtensionController.java +++ b/slideshowfx-image-extension/src/main/java/com/twasyl/slideshowfx/content/extension/image/controllers/ImageContentExtensionController.java @@ -1,9 +1,11 @@ package com.twasyl.slideshowfx.content.extension.image.controllers; +import com.twasyl.slideshowfx.content.extension.AbstractContentExtensionController; import com.twasyl.slideshowfx.osgi.OSGiManager; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.image.Image; @@ -28,7 +30,7 @@ * @version 1.2 * @since SlideshowFX 1.0 */ -public class ImageContentExtensionController implements Initializable { +public class ImageContentExtensionController extends AbstractContentExtensionController { private static final Logger LOGGER = Logger.getLogger(ImageContentExtensionController.class.getName()); public static final FileChooser.ExtensionFilter IMAGES_FILES = new FileChooser.ExtensionFilter("Image files", "*.png", "*.bmp", "*.jpg", "*.jpeg", "*.gif", "*.svg"); public static final FileFilter IMAGE_FILTER = new FileFilter() { @@ -197,7 +199,7 @@ private void defineContextMenuForImageButton(final ToggleButton button) { } /** - * Buils an {@link Alert} to be used in order to display information. + * Builds an {@link Alert} to be used in order to display information. * * @param type The type of the alert to build. * @param title The title of the alert. @@ -247,6 +249,14 @@ public String getSelectedFileUrl() { return url; } + @Override + public ReadOnlyBooleanProperty areInputsValid() { + final ReadOnlyBooleanWrapper property = new ReadOnlyBooleanWrapper(); + property.bind(this.imagesGroup.selectedToggleProperty().isNotNull()); + + return property; + } + @Override public void initialize(URL location, ResourceBundle resources) { List images = this.lookupResources(); diff --git a/SlideshowFX-image-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/image/fxml/ImageContentExtension.fxml b/slideshowfx-image-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/image/fxml/ImageContentExtension.fxml similarity index 100% rename from SlideshowFX-image-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/image/fxml/ImageContentExtension.fxml rename to slideshowfx-image-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/image/fxml/ImageContentExtension.fxml diff --git a/slideshowfx-java-executor/build.gradle b/slideshowfx-java-executor/build.gradle new file mode 100644 index 00000000..fa98a7ff --- /dev/null +++ b/slideshowfx-java-executor/build.gradle @@ -0,0 +1,22 @@ +description = 'Snippet executor allowing to execute some Java inside a SlideshowFX presentation' +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-snippet-executor') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-utils') +} + +ext.isPlugin = true +ext.isSnippetExecutor = true +ext.bundle = [ + name : 'SlideshowFX Java executor', + symbolicName : 'com.twasyl.slideshowfx.snippet.executor.java', + description : 'Allow to execute Java code inside a presentation', + activator : 'com.twasyl.slideshowfx.snippet.executor.java.activator.JavaSnippetExecutorActivator', + exportPackage : 'com.twasyl.slideshowfx.snippet.executor.java,com.twasyl.slideshowfx.snippet.executor.java.activator', + setupWizardLabel: 'Java' +] \ No newline at end of file diff --git a/SlideshowFX-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutor.java b/slideshowfx-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutor.java similarity index 100% rename from SlideshowFX-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutor.java rename to slideshowfx-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutor.java diff --git a/SlideshowFX-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorOptions.java b/slideshowfx-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorOptions.java similarity index 100% rename from SlideshowFX-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorOptions.java rename to slideshowfx-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorOptions.java diff --git a/SlideshowFX-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/activator/JavaSnippetExecutorActivator.java b/slideshowfx-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/activator/JavaSnippetExecutorActivator.java similarity index 100% rename from SlideshowFX-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/activator/JavaSnippetExecutorActivator.java rename to slideshowfx-java-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/java/activator/JavaSnippetExecutorActivator.java diff --git a/SlideshowFX-java-executor/src/main/resources/META-INF/icon.png b/slideshowfx-java-executor/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-java-executor/src/main/resources/META-INF/icon.png rename to slideshowfx-java-executor/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-java-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorTest.java b/slideshowfx-java-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorTest.java similarity index 84% rename from SlideshowFX-java-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorTest.java rename to slideshowfx-java-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorTest.java index 58ae5f9e..785d4048 100644 --- a/SlideshowFX-java-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorTest.java +++ b/slideshowfx-java-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/java/JavaSnippetExecutorTest.java @@ -1,13 +1,13 @@ package com.twasyl.slideshowfx.snippet.executor.java; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; -import com.twasyl.slideshowfx.utils.ResourceHelper; -import org.junit.Test; +import com.twasyl.slideshowfx.utils.io.IOUtils; +import org.junit.jupiter.api.Test; import java.io.IOException; import static com.twasyl.slideshowfx.snippet.executor.java.JavaSnippetExecutor.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the class {@link JavaSnippetExecutor}. @@ -116,7 +116,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName final CodeSnippet snippet = new CodeSnippet(); snippet.setCode("public static void main(String ... args) {\n\tSystem.out.println(\"Hello\");\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName_expected.txt"); + final String expected = IOUtils.read(JavaSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -127,7 +127,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMain() throws IOExcepti snippet.setCode("public static void main(String ... args) {\n\tSystem.out.println(\"Hello\");\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(JavaSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -139,7 +139,7 @@ public void buildSourceCodeWithoutWrapInMain() throws IOException { snippet.setCode("public static void main(String ... args) {\n\tSystem.out.println(\"Hello\");\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(JavaSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -152,7 +152,7 @@ public void buildSourceCode() throws IOException { snippet.setCode("System.out.println(\"Hello\");"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCode_expected.txt"); + final String expected = IOUtils.read(JavaSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCode_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } } diff --git a/SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName_expected.txt b/slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName_expected.txt similarity index 100% rename from SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName_expected.txt rename to slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutClassName_expected.txt diff --git a/SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt b/slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt rename to slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutWrapInMain_expected.txt b/slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutWrapInMain_expected.txt rename to slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCodeWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCode_expected.txt b/slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCode_expected.txt similarity index 100% rename from SlideshowFX-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCode_expected.txt rename to slideshowfx-java-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/java/buildSourceCode_expected.txt diff --git a/slideshowfx-javascript-executor/build.gradle b/slideshowfx-javascript-executor/build.gradle new file mode 100644 index 00000000..0c0a20bb --- /dev/null +++ b/slideshowfx-javascript-executor/build.gradle @@ -0,0 +1,20 @@ +description = 'Snippet executor allowing to execute some Javascript inside a SlideshowFX presentation' +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-snippet-executor') + implementation project(':slideshowfx-plugin') +} + +ext.isPlugin = true +ext.isSnippetExecutor = true +ext.bundle = [ + name : 'SlideshowFX JavaScript executor', + symbolicName : 'com.twasyl.slideshowfx.snippet.executor.javascript', + description : 'Allow to execute JavaScript code inside a presentation', + activator : 'com.twasyl.slideshowfx.snippet.executor.javascript.activator.JavaScriptSnippetExecutorActivator', + exportPackage : 'com.twasyl.slideshowfx.snippet.executor.javascript,com.twasyl.slideshowfx.snippet.executor.javascript.activator', + setupWizardLabel: 'JavaScript' +] \ No newline at end of file diff --git a/SlideshowFX-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutor.java b/slideshowfx-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutor.java similarity index 100% rename from SlideshowFX-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutor.java rename to slideshowfx-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutor.java diff --git a/SlideshowFX-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutorOptions.java b/slideshowfx-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutorOptions.java similarity index 100% rename from SlideshowFX-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutorOptions.java rename to slideshowfx-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/JavaScriptSnippetExecutorOptions.java diff --git a/SlideshowFX-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/activator/JavaScriptSnippetExecutorActivator.java b/slideshowfx-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/activator/JavaScriptSnippetExecutorActivator.java similarity index 100% rename from SlideshowFX-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/activator/JavaScriptSnippetExecutorActivator.java rename to slideshowfx-javascript-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/javascript/activator/JavaScriptSnippetExecutorActivator.java diff --git a/SlideshowFX-javascript-executor/src/main/resources/META-INF/icon.png b/slideshowfx-javascript-executor/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-javascript-executor/src/main/resources/META-INF/icon.png rename to slideshowfx-javascript-executor/src/main/resources/META-INF/icon.png diff --git a/slideshowfx-kotlin-executor/build.gradle b/slideshowfx-kotlin-executor/build.gradle new file mode 100644 index 00000000..d3f36779 --- /dev/null +++ b/slideshowfx-kotlin-executor/build.gradle @@ -0,0 +1,22 @@ +description = 'Snippet executor allowing to execute some Kotlin inside a SlideshowFX presentation' +version = '1.1' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-snippet-executor') + implementation project(':slideshowfx-plugin') + implementation project(':slideshowfx-utils') +} + +ext.isPlugin = true +ext.isSnippetExecutor = true +ext.bundle = [ + name : 'SlideshowFX Kotlin executor', + symbolicName : 'com.twasyl.slideshowfx.snippet.executor.kotlin', + description : 'Allow to execute Kotlin code inside a presentation', + activator : 'com.twasyl.slideshowfx.snippet.executor.kotlin.activator.KotlinSnippetExecutorActivator', + exportPackage : 'com.twasyl.slideshowfx.snippet.executor.kotlin,com.twasyl.slideshowfx.snippet.executor.kotlin.activator', + setupWizardLabel: 'Kotlin' +] \ No newline at end of file diff --git a/SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutor.java b/slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutor.java old mode 100644 new mode 100755 similarity index 96% rename from SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutor.java rename to slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutor.java index e73fc531..7ffbd031 --- a/SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutor.java +++ b/slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutor.java @@ -1,5 +1,6 @@ package com.twasyl.slideshowfx.snippet.executor.kotlin; +import com.sun.javafx.PlatformUtil; import com.twasyl.slideshowfx.global.configuration.GlobalConfiguration; import com.twasyl.slideshowfx.snippet.executor.AbstractSnippetExecutor; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; @@ -25,7 +26,7 @@ * This implementation is identified with the code {@code KOTLIN}. * * @author Thierry Wasyczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class KotlinSnippetExecutor extends AbstractSnippetExecutor { @@ -141,7 +142,10 @@ public ObservableList execute(final CodeSnippet codeSnippet) { // Compile the Kotlin class final String jarFile = "Snippet.jar"; - final File koltincExecutable = new File(this.getOptions().getKotlinHome(), "bin/kotlinc"); + + final File koltincExecutable = PlatformUtil.isWindows() ? + new File(this.getOptions().getKotlinHome(), "bin/kotlinc.bat") : + new File(this.getOptions().getKotlinHome(), "bin/kotlinc"); final String[] compilationCommand = {koltincExecutable.getAbsolutePath(), codeFile.getName(), "-include-runtime", "-d", jarFile}; @@ -175,7 +179,10 @@ public ObservableList execute(final CodeSnippet codeSnippet) { // Execute the Kotlin class only if the compilation was successful if(process != null && process.exitValue() == 0) { - final File kotlinExecutable = new File(this.getOptions().getKotlinHome(), "bin/kotlin"); + final File kotlinExecutable = PlatformUtil.isWindows() ? + new File(this.getOptions().getKotlinHome(), "bin/kotlin.bat") : + new File(this.getOptions().getKotlinHome(), "bin/kotlin"); + final String[] executionCommand = {kotlinExecutable.getAbsolutePath(), jarFile}; try { diff --git a/SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorOptions.java b/slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorOptions.java similarity index 100% rename from SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorOptions.java rename to slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorOptions.java diff --git a/SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/activator/KotlinSnippetExecutorActivator.java b/slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/activator/KotlinSnippetExecutorActivator.java similarity index 100% rename from SlideshowFX-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/activator/KotlinSnippetExecutorActivator.java rename to slideshowfx-kotlin-executor/src/main/java/com/twasyl/slideshowfx/snippet/executor/kotlin/activator/KotlinSnippetExecutorActivator.java diff --git a/SlideshowFX-kotlin-executor/src/main/resources/META-INF/icon.png b/slideshowfx-kotlin-executor/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-kotlin-executor/src/main/resources/META-INF/icon.png rename to slideshowfx-kotlin-executor/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-kotlin-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorTest.java b/slideshowfx-kotlin-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorTest.java similarity index 84% rename from SlideshowFX-kotlin-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorTest.java rename to slideshowfx-kotlin-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorTest.java index 62d814f8..4a24ee6c 100644 --- a/SlideshowFX-kotlin-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorTest.java +++ b/slideshowfx-kotlin-executor/src/test/java/com/twasyl/slideshowfx/snippet/executor/kotlin/KotlinSnippetExecutorTest.java @@ -1,13 +1,13 @@ package com.twasyl.slideshowfx.snippet.executor.kotlin; import com.twasyl.slideshowfx.snippet.executor.CodeSnippet; -import com.twasyl.slideshowfx.utils.ResourceHelper; -import org.junit.Test; +import com.twasyl.slideshowfx.utils.io.IOUtils; +import org.junit.jupiter.api.Test; import java.io.IOException; import static com.twasyl.slideshowfx.snippet.executor.kotlin.KotlinSnippetExecutor.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * Tests the class {@link KotlinSnippetExecutor}. @@ -121,7 +121,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage() final CodeSnippet snippet = new CodeSnippet(); snippet.setCode("fun main(args: Array) {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage_expected.txt"); + final String expected = IOUtils.read(KotlinSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -132,7 +132,7 @@ public void buildSourceCodeWithoutImportsAndWithoutWrapInMain() throws IOExcepti snippet.setCode("fun main(args: Array) {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(KotlinSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -144,7 +144,7 @@ public void buildSourceCodeWithoutWrapInMain() throws IOException { snippet.setCode("fun main(args: Array) {\n\tprintln(\"Hello\")\n}"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutWrapInMain_expected.txt"); + final String expected = IOUtils.read(KotlinSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutWrapInMain_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } @@ -157,7 +157,7 @@ public void buildSourceCode() throws IOException { snippet.setCode("println(\"Hello\")"); - final String expected = ResourceHelper.readResource("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCode_expected.txt"); + final String expected = IOUtils.read(KotlinSnippetExecutorTest.class.getResourceAsStream("/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCode_expected.txt")); assertEquals(expected, snippetExecutor.buildSourceCode(snippet)); } } diff --git a/SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage_expected.txt b/slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage_expected.txt similarity index 100% rename from SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage_expected.txt rename to slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMainAndWithoutPackage_expected.txt diff --git a/SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt b/slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt rename to slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutImportsAndWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutWrapInMain_expected.txt b/slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutWrapInMain_expected.txt similarity index 100% rename from SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutWrapInMain_expected.txt rename to slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCodeWithoutWrapInMain_expected.txt diff --git a/SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCode_expected.txt b/slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCode_expected.txt similarity index 100% rename from SlideshowFX-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCode_expected.txt rename to slideshowfx-kotlin-executor/src/test/resources/com/twasyl/slideshowfx/snippet/executor/kotlin/buildSourceCode_expected.txt diff --git a/slideshowfx-link-extension/build.gradle b/slideshowfx-link-extension/build.gradle new file mode 100755 index 00000000..95f9bad6 --- /dev/null +++ b/slideshowfx-link-extension/build.gradle @@ -0,0 +1,29 @@ +description = 'Extension allowing to insert links inside a SlideshowFX presentation' +version = '1.2' + +apply plugin: 'java-library' + +dependencies { + api project(':slideshowfx-content-extension') + implementation project(':slideshowfx-icons') + api project(':slideshowfx-markup') + api project(':slideshowfx-plugin') + api project(':slideshowfx-ui-controls') + + testCompile project(':slideshowfx-html') + testCompile project(':slideshowfx-markdown') + testCompile project(':slideshowfx-textile') + testCompile "org.mockito:mockito-core:${rootProject.ext.dependencies.mockito.version}" +} + +ext.isPlugin = true +ext.isContentExtension = true +ext.bundle = [ + name : 'SlideshowFX link extension', + symbolicName : 'com.twasyl.slideshowfx.content.extension.link', + description : 'Support for inserting links in slides', + activator : 'com.twasyl.slideshowfx.content.extension.link.activator.LinkContentExtensionActivator', + exportPackage : 'com.twasyl.slideshowfx.content.extension.link.controllers,com.twasyl.slideshowfx.content.extension.link,com.twasyl.slideshowfx.content.extension.link.activator', + setupWizardLabel : 'Link', + setupWizardIconName: 'LINK' +] \ No newline at end of file diff --git a/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/LinkContentExtension.java b/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/LinkContentExtension.java new file mode 100755 index 00000000..4b189fc9 --- /dev/null +++ b/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/LinkContentExtension.java @@ -0,0 +1,130 @@ +package com.twasyl.slideshowfx.content.extension.link; + +import com.twasyl.slideshowfx.content.extension.AbstractContentExtension; +import com.twasyl.slideshowfx.content.extension.link.controllers.LinkContentExtensionController; +import com.twasyl.slideshowfx.markup.IMarkup; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.fxml.FXMLLoader; +import javafx.scene.layout.Pane; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.twasyl.slideshowfx.icons.Icon.LINK; + +/** + * The LinkContentExtension extends the AbstractContentExtension. It allows to build a content containing links to insert + * inside a SlideshowFX presentation. + * This extension doesn't use other resources + * This extension supports HTML and Textile markup languages. + * + * @author Thierry Wasylczenko + * @version 1.2 + * @since SlideshowFX 1.0 + */ +public class LinkContentExtension extends AbstractContentExtension { + private static final Logger LOGGER = Logger.getLogger(LinkContentExtension.class.getName()); + + protected LinkContentExtensionController controller; + + public LinkContentExtension() { + super("LINK", null, + LINK, + "Insert a link", + "Insert a link"); + } + + @Override + public Pane getUI() { + FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("/com/twasyl/slideshowfx/content/extension/link/fxml/LinkContentExtension.fxml")); + Pane root = null; + + try { + loader.setClassLoader(getClass().getClassLoader()); + root = loader.load(); + this.controller = loader.getController(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Can not load UI for LinkContentExtension", e); + } + + return root; + } + + @Override + public String buildContentString(IMarkup markup) { + final StringBuilder builder = new StringBuilder(); + final boolean addressNotEmpty = this.controller.getAddress() != null && !this.controller.getAddress().trim().isEmpty(); + + if (addressNotEmpty) { + if (markup == null || "HTML".equals(markup.getCode())) { + builder.append(this.buildDefaultContentString()); + } else if ("TEXTILE".equals(markup.getCode())) { + builder.append(this.buildTextileContentString()); + } else if ("MARKDOWN".equals(markup.getCode())) { + builder.append(this.buildMarkdownContentString()); + } else { + builder.append(this.buildDefaultContentString()); + } + } + return builder.toString(); + } + + @Override + public String buildDefaultContentString() { + + final StringBuilder builder = new StringBuilder(); + builder.append("") + .append(this.controller.getText() == null || this.controller.getText().trim().isEmpty() ? + this.controller.getAddress() : this.controller.getText()) + .append(""); + + return builder.toString(); + } + + /** + * Build the string representing a link in textile. + * + * @return The built string in the textile markup language. + */ + private String buildTextileContentString() { + final StringBuilder builder = new StringBuilder("\""); + + if (this.controller.getText() == null || this.controller.getText().trim().isEmpty()) { + builder.append(this.controller.getAddress()); + } else { + builder.append(this.controller.getText()); + } + + builder.append("\":").append(this.controller.getAddress()); + return builder.toString(); + } + + /** + * Build the string representing a link in markdown. + * + * @return The built string in the markdown language. + */ + private String buildMarkdownContentString() { + final StringBuilder builder = new StringBuilder(); + + final boolean emptyText = this.controller.getText() == null || this.controller.getText().trim().isEmpty(); + + if (!emptyText) { + builder.append("[").append(this.controller.getText().trim()).append("]("); + } + + builder.append(this.controller.getAddress()); + + if (!emptyText) { + builder.append(")"); + } + + return builder.toString(); + } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + return this.controller.areInputsValid(); + } +} diff --git a/SlideshowFX-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/activator/LinkContentExtensionActivator.java b/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/activator/LinkContentExtensionActivator.java similarity index 100% rename from SlideshowFX-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/activator/LinkContentExtensionActivator.java rename to slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/activator/LinkContentExtensionActivator.java diff --git a/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/controllers/LinkContentExtensionController.java b/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/controllers/LinkContentExtensionController.java new file mode 100755 index 00000000..c80510d3 --- /dev/null +++ b/slideshowfx-link-extension/src/main/java/com/twasyl/slideshowfx/content/extension/link/controllers/LinkContentExtensionController.java @@ -0,0 +1,115 @@ +package com.twasyl.slideshowfx.content.extension.link.controllers; + +import com.twasyl.slideshowfx.content.extension.AbstractContentExtensionController; +import com.twasyl.slideshowfx.ui.controls.ExtendedTextField; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.fxml.FXML; +import javafx.scene.input.Clipboard; + +import java.net.URL; +import java.util.ResourceBundle; + +import static com.twasyl.slideshowfx.ui.controls.validators.Validators.isNotEmpty; + +/** + * This class is the controller used by the {@code QuoteContentExtension.fxml} file. The field containing the address + * in the UI will be initialized by the content of the {@link Clipboard} if it contains a text having an URL form. + * + * @author Thierry Wasylczenko + * @version 1.2 + * @since SlideshowFX 1.0 + */ +public class LinkContentExtensionController extends AbstractContentExtensionController { + + @FXML + private ExtendedTextField address; + @FXML + private ExtendedTextField text; + + /** + * Get the address of the link entered in the UI. + * + * @return The address of the link entered in the UI. + */ + public String getAddress() { + return this.address.getText(); + } + + /** + * Get the text of the link inserted in the UI. + * + * @return The text of the link inserted in the UI. + */ + public String getText() { + return this.text.getText(); + } + + /** + * Get the URL that is present in the system clipboard. This method ensures that if the + * {@link Clipboard#hasUrl()} returns {@code true}, then URL is truly an URL. + * If the clipboard doesn't contain an URL according both {@link Clipboard#hasUrl()} and + * {@link Clipboard#getUrl()}, then the text is checked in order to determine if there is a true + * URL as text in the clipboard. + * + * @return The URL or {@code null} if none. + */ + private String getClipboardURL() { + boolean hasUrl = Clipboard.getSystemClipboard().hasUrl(); + String url = null; + + if (hasUrl) { + if (isTextAnURL(Clipboard.getSystemClipboard().getUrl())) { + url = Clipboard.getSystemClipboard().getUrl(); + } else { + hasUrl = false; + } + } + + if (!hasUrl && Clipboard.getSystemClipboard().hasString()) { + if (isTextAnURL(Clipboard.getSystemClipboard().getString())) { + url = Clipboard.getSystemClipboard().getString(); + } + } + + return url; + } + + /** + * Check if a given text is an URL. The test is case un-sensitive and check is the text starts with one of the + * following: + *
    + *
  • http://
  • + *
  • https://
  • + *
  • www.
  • + *
+ * + * @param text The text to test. + * @return {@code true} if the text has an URL form, {@code false} otherwise, including {@code null}. + */ + private boolean isTextAnURL(final String text) { + final String lowerCasedText = text != null ? text.toLowerCase() : null; + boolean isURL = lowerCasedText != null && ( + lowerCasedText.startsWith("http://") || + lowerCasedText.startsWith("https://") || + lowerCasedText.startsWith("www.") + ); + return isURL; + } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + final ReadOnlyBooleanWrapper property = new ReadOnlyBooleanWrapper(); + property.bind(this.address.validProperty()); + + return property; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + this.address.setValidator(isNotEmpty()); + final String url = getClipboardURL(); + + if (url != null) this.address.setText(url); + } +} diff --git a/slideshowfx-link-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/link/fxml/LinkContentExtension.fxml b/slideshowfx-link-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/link/fxml/LinkContentExtension.fxml new file mode 100755 index 00000000..a6199b0e --- /dev/null +++ b/slideshowfx-link-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/link/fxml/LinkContentExtension.fxml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/HtmlLinkContentExtensionTest.java b/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/HtmlLinkContentExtensionTest.java new file mode 100755 index 00000000..f332f15a --- /dev/null +++ b/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/HtmlLinkContentExtensionTest.java @@ -0,0 +1,87 @@ +package com.twasyl.slideshowfx.content.extension.link; + +import com.twasyl.slideshowfx.content.extension.link.controllers.LinkContentExtensionController; +import com.twasyl.slideshowfx.markup.IMarkup; +import com.twasyl.slideshowfx.markup.html.HtmlMarkup; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class tests the {@link LinkContentExtension} using an HTML markup. + * @author Thierry Wasylczenko + * @since SlideshowFX 1.3 + */ +public class HtmlLinkContentExtensionTest { + + private static IMarkup markup; + private static LinkContentExtension linkContentExtension; + + @BeforeAll + public static void setUp() { + markup = new HtmlMarkup(); + linkContentExtension = new LinkContentExtension(); + linkContentExtension.controller = mock(LinkContentExtensionController.class); + } + + @Test + public void withAddressAndTextAndNullMarkup() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFX website"); + + final String contentString = linkContentExtension.buildContentString(null); + final String expected = "SlideshowFX website"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFX website"); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "SlideshowFX website"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndNullText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn(null); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndEmptyText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn(""); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withNullAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn(null); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFW website"); + + final String contentString = linkContentExtension.buildContentString(markup); + assertEquals("", contentString); + } + + @Test + public void withEmptyAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn(""); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFW website"); + + final String contentString = linkContentExtension.buildContentString(markup); + assertEquals("", contentString); + } +} \ No newline at end of file diff --git a/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/MarkdownLinkContentExtensionTest.java b/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/MarkdownLinkContentExtensionTest.java new file mode 100755 index 00000000..08914c33 --- /dev/null +++ b/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/MarkdownLinkContentExtensionTest.java @@ -0,0 +1,87 @@ +package com.twasyl.slideshowfx.content.extension.link; + +import com.twasyl.slideshowfx.content.extension.link.controllers.LinkContentExtensionController; +import com.twasyl.slideshowfx.markup.IMarkup; +import com.twasyl.slideshowfx.markup.markdown.MarkdownMarkup; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class tests the {@link LinkContentExtension} using a markdown markup. + * @author Thierry Wasylczenko + * @since SlideshowFX 1.3 + */ +public class MarkdownLinkContentExtensionTest { + + private static IMarkup markup; + private static LinkContentExtension linkContentExtension; + + @BeforeAll + public static void setUp() { + markup = new MarkdownMarkup(); + linkContentExtension = new LinkContentExtension(); + linkContentExtension.controller = mock(LinkContentExtensionController.class); + } + + @Test + public void withAddressAndTextAndNullMarkup() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFX website"); + + final String contentString = linkContentExtension.buildContentString(null); + final String expected = "SlideshowFX website"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFX website"); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "[SlideshowFX website](http://slideshowfx.github.io)"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndNullText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn(null); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndEmptyText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn(""); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withNullAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn(null); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFW website"); + + final String contentString = linkContentExtension.buildContentString(markup); + assertEquals("", contentString); + } + + @Test + public void withEmptyAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn(""); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFW website"); + + final String contentString = linkContentExtension.buildContentString(markup); + assertEquals("", contentString); + } +} \ No newline at end of file diff --git a/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/TextileLinkContentExtensionTest.java b/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/TextileLinkContentExtensionTest.java new file mode 100755 index 00000000..5f5e9da8 --- /dev/null +++ b/slideshowfx-link-extension/src/test/java/com/twasyl/slideshowfx/content/extension/link/TextileLinkContentExtensionTest.java @@ -0,0 +1,87 @@ +package com.twasyl.slideshowfx.content.extension.link; + +import com.twasyl.slideshowfx.content.extension.link.controllers.LinkContentExtensionController; +import com.twasyl.slideshowfx.markup.IMarkup; +import com.twasyl.slideshowfx.markup.textile.TextileMarkup; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * This class tests the {@link LinkContentExtension} using a textile markup. + * @author Thierry Wasylczenko + * @since SlideshowFX 1.3 + */ +public class TextileLinkContentExtensionTest { + + private static IMarkup markup; + private static LinkContentExtension linkContentExtension; + + @BeforeAll + public static void setUp() { + markup = new TextileMarkup(); + linkContentExtension = new LinkContentExtension(); + linkContentExtension.controller = mock(LinkContentExtensionController.class); + } + + @Test + public void withAddressAndTextAndNullMarkup() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFX website"); + + final String contentString = linkContentExtension.buildContentString(null); + final String expected = "SlideshowFX website"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFX website"); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "\"SlideshowFX website\":http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndNullText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn(null); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "\"http://slideshowfx.github.io\":http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withAddressAndEmptyText() { + when(linkContentExtension.controller.getAddress()).thenReturn("http://slideshowfx.github.io"); + when(linkContentExtension.controller.getText()).thenReturn(""); + + final String contentString = linkContentExtension.buildContentString(markup); + final String expected = "\"http://slideshowfx.github.io\":http://slideshowfx.github.io"; + assertEquals(expected, contentString); + } + + @Test + public void withNullAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn(null); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFW website"); + + final String contentString = linkContentExtension.buildContentString(markup); + assertEquals("", contentString); + } + + @Test + public void withEmptyAddressAndText() { + when(linkContentExtension.controller.getAddress()).thenReturn(""); + when(linkContentExtension.controller.getText()).thenReturn("SlideshowFW website"); + + final String contentString = linkContentExtension.buildContentString(markup); + assertEquals("", contentString); + } +} \ No newline at end of file diff --git a/slideshowfx-logs/build.gradle b/slideshowfx-logs/build.gradle new file mode 100644 index 00000000..983330f3 --- /dev/null +++ b/slideshowfx-logs/build.gradle @@ -0,0 +1,4 @@ +description = 'Module allowing to define the log mechanism for the application' +version = '1.0' + +apply plugin: 'java-library' \ No newline at end of file diff --git a/SlideshowFX-logs/src/main/java/com/twasyl/slideshowfx/logs/SlideshowFXHandler.java b/slideshowfx-logs/src/main/java/com/twasyl/slideshowfx/logs/SlideshowFXHandler.java similarity index 100% rename from SlideshowFX-logs/src/main/java/com/twasyl/slideshowfx/logs/SlideshowFXHandler.java rename to slideshowfx-logs/src/main/java/com/twasyl/slideshowfx/logs/SlideshowFXHandler.java diff --git a/slideshowfx-markdown/build.gradle b/slideshowfx-markdown/build.gradle new file mode 100644 index 00000000..f793a180 --- /dev/null +++ b/slideshowfx-markdown/build.gradle @@ -0,0 +1,28 @@ +description = 'Extension allowing to define slide\'s content using the markdown syntax' +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-markup') + + compile "com.github.rjeschke:txtmark:${rootProject.ext.dependencies.markdown.version}" +} + +ext.isPlugin = true +ext.isMarkupPlugin = true +ext.bundle = [ + name : 'SlideshowFX markdown support', + symbolicName : 'com.twasyl.slideshowfx.markup.markdown', + description : 'Support markdown for defining slide\'s content', + activator : 'com.twasyl.slideshowfx.markup.markdown.activator.MarkdownActivator', + classPath : configurations.compile.resolve().collect { artifact -> artifact.name }.join(',').concat(',.'), + exportPackage : 'com.twasyl.slideshowfx.markup.markdown,com.twasyl.slideshowfx.markup.markdown.activator', + setupWizardLabel: 'Markdown' +] + +jar { + from(configurations.compile.resolve().collect()) { + include '*' + } +} \ No newline at end of file diff --git a/SlideshowFX-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkup.java b/slideshowfx-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkup.java similarity index 100% rename from SlideshowFX-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkup.java rename to slideshowfx-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkup.java diff --git a/SlideshowFX-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/activator/MarkdownActivator.java b/slideshowfx-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/activator/MarkdownActivator.java similarity index 100% rename from SlideshowFX-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/activator/MarkdownActivator.java rename to slideshowfx-markdown/src/main/java/com/twasyl/slideshowfx/markup/markdown/activator/MarkdownActivator.java diff --git a/SlideshowFX-markdown/src/main/resources/META-INF/icon.png b/slideshowfx-markdown/src/main/resources/META-INF/icon.png similarity index 100% rename from SlideshowFX-markdown/src/main/resources/META-INF/icon.png rename to slideshowfx-markdown/src/main/resources/META-INF/icon.png diff --git a/SlideshowFX-markdown/src/test/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkupTest.java b/slideshowfx-markdown/src/test/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkupTest.java similarity index 66% rename from SlideshowFX-markdown/src/test/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkupTest.java rename to slideshowfx-markdown/src/test/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkupTest.java index 7967ebbb..b4bd30b5 100644 --- a/SlideshowFX-markdown/src/test/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkupTest.java +++ b/slideshowfx-markdown/src/test/java/com/twasyl/slideshowfx/markup/markdown/MarkdownMarkupTest.java @@ -1,9 +1,10 @@ package com.twasyl.slideshowfx.markup.markdown; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Thierry Wasylczenko @@ -13,47 +14,53 @@ public class MarkdownMarkupTest { private static MarkdownMarkup markup; - @BeforeClass + @BeforeAll public static void setUp() { markup = new MarkdownMarkup(); } - @Test(expected = IllegalArgumentException.class) + @Test public void generateWithNull() { - markup.convertAsHtml(null); + assertThrows(IllegalArgumentException.class, () -> markup.convertAsHtml(null)); } - @Test public void generateH1() { + @Test + public void generateH1() { final String result = markup.convertAsHtml("# A title"); assertEquals("

A title

", result); } - @Test public void generateH2() { + @Test + public void generateH2() { final String result = markup.convertAsHtml("## A title"); assertEquals("

A title

", result); } - @Test public void generateInlineCode() { + @Test + public void generateInlineCode() { final String result = markup.convertAsHtml("`public class Java { }`"); assertEquals("

public class Java { }

", result); } - @Test public void generateCodeBloc() { + @Test + public void generateCodeBloc() { final String result = markup.convertAsHtml(" final String s;"); assertEquals("
final String s;\n
", result); } - @Test public void generateStrong() { + @Test + public void generateStrong() { final String result = markup.convertAsHtml("*Strong text*"); assertEquals("

Strong text

", result); } - @Test public void generateUnorderedList() { + @Test + public void generateUnorderedList() { final String result = markup.convertAsHtml("* One\n* Two"); assertEquals("
    \n
  • One
  • \n
  • Two
  • \n
", result); diff --git a/slideshowfx-markup/build.gradle b/slideshowfx-markup/build.gradle new file mode 100644 index 00000000..bdef7c9d --- /dev/null +++ b/slideshowfx-markup/build.gradle @@ -0,0 +1,8 @@ +description = "Module defining a plugin" +version = '1.0' + +apply plugin: 'java-library' + +dependencies { + api project(':slideshowfx-plugin') +} \ No newline at end of file diff --git a/SlideshowFX-markup/src/main/java/com/twasyl/slideshowfx/markup/AbstractMarkup.java b/slideshowfx-markup/src/main/java/com/twasyl/slideshowfx/markup/AbstractMarkup.java similarity index 100% rename from SlideshowFX-markup/src/main/java/com/twasyl/slideshowfx/markup/AbstractMarkup.java rename to slideshowfx-markup/src/main/java/com/twasyl/slideshowfx/markup/AbstractMarkup.java diff --git a/SlideshowFX-markup/src/main/java/com/twasyl/slideshowfx/markup/IMarkup.java b/slideshowfx-markup/src/main/java/com/twasyl/slideshowfx/markup/IMarkup.java similarity index 100% rename from SlideshowFX-markup/src/main/java/com/twasyl/slideshowfx/markup/IMarkup.java rename to slideshowfx-markup/src/main/java/com/twasyl/slideshowfx/markup/IMarkup.java diff --git a/slideshowfx-osgi/build.gradle b/slideshowfx-osgi/build.gradle new file mode 100755 index 00000000..435c9974 --- /dev/null +++ b/slideshowfx-osgi/build.gradle @@ -0,0 +1,12 @@ +description = 'Module allowing to add OSGi support in the application' +version = '1.2' + +apply plugin: 'java-library' + +dependencies { + implementation project(':slideshowfx-engines') + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-plugin') + + api "org.apache.felix:org.apache.felix.framework:${rootProject.ext.dependencies.felix.version}" +} \ No newline at end of file diff --git a/slideshowfx-osgi/src/main/java/com/twasyl/slideshowfx/osgi/OSGiManager.java b/slideshowfx-osgi/src/main/java/com/twasyl/slideshowfx/osgi/OSGiManager.java new file mode 100755 index 00000000..e1cd5803 --- /dev/null +++ b/slideshowfx-osgi/src/main/java/com/twasyl/slideshowfx/osgi/OSGiManager.java @@ -0,0 +1,470 @@ +package com.twasyl.slideshowfx.osgi; + +import com.twasyl.slideshowfx.engine.presentation.Presentations; +import com.twasyl.slideshowfx.global.configuration.GlobalConfiguration; +import com.twasyl.slideshowfx.plugin.IPlugin; +import com.twasyl.slideshowfx.plugin.InstalledPlugin; +import org.osgi.framework.*; +import org.osgi.framework.launch.Framework; +import org.osgi.framework.launch.FrameworkFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; +import static org.osgi.framework.Constants.*; + +/** + * This class manages all OSGi bundles: from installation to uninstallation. It also starts the OSGi container as well + * as it can stop it properly. + * + * @author Thierry Wasylczenko + * @version 1.2 + * @since SlideshowFX 1.0 + */ +public class OSGiManager { + private static final Logger LOGGER = Logger.getLogger(OSGiManager.class.getName()); + private static OSGiManager singleton = null; + + public static final String PRESENTATION_FOLDER = "presentation.folder"; + public static final String PRESENTATION_RESOURCES_FOLDER = "presentation.resources.folder"; + + protected Framework osgiFramework; + protected File pluginsDirectory; + protected File osgiCache; + + /** + * Default constructor of the class. + */ + protected OSGiManager() { + this.pluginsDirectory = GlobalConfiguration.getPluginsDirectory(); + this.osgiCache = new File(GlobalConfiguration.getApplicationDirectory(),"felix-cache"); + } + + public static final synchronized OSGiManager getInstance() { + if(OSGiManager.singleton == null) { + OSGiManager.singleton = new OSGiManager(); + } + + return OSGiManager.singleton; + } + + /** + * Start the OSGi container. + */ + public void start() { + final Map configurationMap = new HashMap<>(); + configurationMap.put(FRAMEWORK_STORAGE_CLEAN, FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); + configurationMap.put(FRAMEWORK_STORAGE, this.osgiCache.getAbsolutePath().replaceAll("\\\\", "/")); + configurationMap.put(FRAMEWORK_BUNDLE_PARENT, FRAMEWORK_BUNDLE_PARENT_APP); + + final StringJoiner bootdelegation = new StringJoiner(","); + bootdelegation.add("com.twasyl.slideshowfx.markup") + .add("com.twasyl.slideshowfx.content.extension") + .add("com.twasyl.slideshowfx.hosting.connector") + .add("com.twasyl.slideshowfx.hosting.connector.io") + .add("com.twasyl.slideshowfx.hosting.connector.exceptions") + .add("com.twasyl.slideshowfx.snippet.executor") + .add("com.twasyl.slideshowfx.osgi") + .add("com.twasyl.slideshowfx.engine.*") + .add("com.twasyl.slideshowfx.global.configuration") + .add("com.twasyl.slideshowfx.utils.*") + .add("com.twasyl.slideshowfx.utils") + .add("com.twasyl.slideshowfx.plugin") + .add("com.twasyl.slideshowfx.server.beans.quiz") + .add("com.twasyl.slideshowfx.icons") + .add("com.twasyl.slideshowfx.ui.controls") + .add("com.twasyl.slideshowfx.ui.controls.validators") + .add("sun.misc") + .add("org.w3c.*") + .add("javax.*") + .add("javafx.*") + .add("com.sun.javafx"); + configurationMap.put(FRAMEWORK_BOOTDELEGATION, bootdelegation.toString()); + + // Starting OSGi + final FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class).iterator().next(); + osgiFramework = frameworkFactory.newFramework(configurationMap); + try { + osgiFramework.start(); + LOGGER.fine("OSGI container has bee started successfully"); + } catch (BundleException | OverlappingFileLockException e) { + LOGGER.log(SEVERE, "Can not start OSGi server", e); + try { + osgiFramework.stop(); + } catch (BundleException e1) { + LOGGER.log(SEVERE, "Can not correctly abort OSGi starting process", e1); + } finally { + this.osgiFramework = null; + } + } + } + + /** + * Start the OSGi container and deploy all plugins in the plugins' directory. + */ + public void startAndDeploy() { + start(); + + if(this.osgiFramework != null) { + // Deploy initially present plugins + if (!this.pluginsDirectory.exists()) { + if (!this.pluginsDirectory.mkdirs()) { + LOGGER.log(SEVERE, "Can not create plugins directory"); + } + } + + if (this.pluginsDirectory.exists()) { + Arrays.stream(this.pluginsDirectory.listFiles((dir, name) -> name.endsWith(".jar"))) + .forEach(file -> { + try { + this.deployBundle(file, false); + } catch (IOException e) { + LOGGER.log(WARNING, "Can not deploy bundle", e); + } + }); + + Arrays.stream(this.osgiFramework.getBundleContext().getBundles()) + .filter(this::isPluginInactive) + .forEach(this::startBundle); + } + } + } + + /** + * Stop the OSGi container. + */ + public void stop() { + if(osgiFramework != null) { + try { + osgiFramework.stop(); + osgiFramework.waitForStop(0); + } catch (BundleException e) { + LOGGER.log(SEVERE, "Can not stop Felix", e); + } catch (InterruptedException e) { + LOGGER.log(SEVERE, "Can not wait for stopping Felix", e); + } + } + } + + /** + * Deploys a bundleFile in the OSGi container and start it. This method copies the given bundleFile to directory of + * plugins and then deploys it. + * If the bundle is already in the plugins' directory, it is simply deployed. + * If the plugin exists in a more recent version, the given plugin will not be installed. + * If the plugin is more recent than an already installed version, the old version is uninstalled and the new one + * is installed. + * + * @param bundleFile The bundleFile to deploy. + * @throws IllegalArgumentException If the bundleFile is not a directory. + * @throws FileNotFoundException If the bundleFile is not found. + * @throws NullPointerException If the bundleFile is null. + * @return The installed service. + */ + public Object deployBundle(File bundleFile) throws IllegalArgumentException, NullPointerException, IOException { + return this.deployBundle(bundleFile, true); + } + + /** + * Deploys a bundleFile in the OSGi container. This method copies the given bundleFile to directory of plugins and then deploys it. + * If the bundle is already in the plugins' directory, it is simply deployed. + * If the plugin exists in a more recent version, the given plugin will not be installed. + * If the plugin is more recent than an already installed version, the old version is uninstalled and the new one + * is installed. + * + * @param bundleFile The bundleFile to deploy. + * @param start Indicate if the bundle should be started. + * @throws IllegalArgumentException If the bundleFile is not a directory. + * @throws FileNotFoundException If the bundleFile is not found. + * @throws NullPointerException If the bundleFile is null. + * @return The installed service. + */ + protected Object deployBundle(final File bundleFile, final boolean start) throws IllegalArgumentException, NullPointerException, IOException { + if(bundleFile == null) throw new NullPointerException("The bundleFile to deploy is null"); + if(!bundleFile.exists()) throw new FileNotFoundException("The bundleFile does not exist"); + if(!bundleFile.isFile()) throw new IllegalArgumentException("The bundleFile has to be a file"); + + if(!bundleFile.getParentFile().toPath().normalize().equals(this.pluginsDirectory.toPath())) { + Files.copy(bundleFile.toPath(), this.pluginsDirectory.toPath().resolve(bundleFile.getName()), StandardCopyOption.REPLACE_EXISTING); + } + + Bundle bundle = null; + try { + bundle = this.osgiFramework.getBundleContext() + .installBundle(String.format("file:%1$s/%2$s", this.pluginsDirectory.getAbsolutePath(), bundleFile.getName())); + } catch (BundleException e) { + LOGGER.log(WARNING, "Can not install bundle", e); + } + + if(bundle != null) { + final boolean isPluginInAnotherVersionInstalled = isPluginInAnotherVersionInstalled(bundle); + final boolean isPluginMostRecent = isPluginMostRecent(bundle); + + if(isPluginInAnotherVersionInstalled && isPluginMostRecent) { + final Bundle pluginInAnotherVersion = getPluginInAnotherVersion(bundle); + + uninstallBundle(pluginInAnotherVersion); + if(start) { + startBundle(bundle); + } + } else if(!isPluginInAnotherVersionInstalled && !isPluginActive(bundle) && isPluginMostRecent && start) { + startBundle(bundle); + } else if(isPluginInAnotherVersionInstalled && !isPluginMostRecent) { + uninstallBundle(bundle); + bundle = null; + } + } + + Object service = null; + if(bundle != null && bundle.getRegisteredServices() != null && bundle.getRegisteredServices().length > 0) { + ServiceReference serviceReference = bundle.getRegisteredServices()[0]; + service = osgiFramework.getBundleContext().getService(serviceReference); + } + + return service; + } + + /** + * Starts a given bundle. The bundle must already have been installed in the OSGi framework. + * @param bundle The bundle to start. + */ + protected void startBundle(Bundle bundle) { + try { + bundle.start(); + } catch (BundleException e) { + LOGGER.log(WARNING, String.format("Can not install bundle [%1$s] in version [%2$s]", bundle.getSymbolicName(), bundle.getVersion()), e); + } + } + + /** + * Uninstall a bundle from the OSGi container. + * @param bundle The bundle to uninstall. + */ + protected void uninstallBundle(Bundle bundle) { + if(bundle != null) { + try { + bundle.uninstall(); + } catch (BundleException e) { + LOGGER.log(WARNING, String.format("Can not uninstall bundle [%1$s] in version [%2$s]", bundle.getSymbolicName(), bundle.getVersion().toString())); + } + + try { + final File bundleFile = new File(new URL(bundle.getLocation()).getFile()); + bundleFile.deleteOnExit(); + } catch (MalformedURLException e) { + LOGGER.log(Level.SEVERE, "Can not determine bundle location", e); + } + } + } + + /** + * Uninstall a bundle from the OSGi container. If the bundle file is found in the OSGi container, then it is + * uninstalled and the bundle file is marked for being deleted at the application's shutdown. + * @param bundleFile The bundle to uninstall. + * @throws FileNotFoundException If the bundle file doesn't exist. + * @throws BundleException If an error occurred while trying to remove the bundle. + */ + public void uninstallBundle(final File bundleFile) throws FileNotFoundException, BundleException { + if(bundleFile == null) throw new NullPointerException("The bundleFile to deploy is null"); + if(!bundleFile.exists()) throw new FileNotFoundException("The bundleFile does not exist"); + if(!bundleFile.isFile()) throw new IllegalArgumentException("The bundleFile has to be a file"); + + final Path bundlePath = bundleFile.toPath().toAbsolutePath(); + final Bundle[] installedBundles = osgiFramework.getBundleContext().getBundles(); + boolean continueSearching = true; + int index = 0; + + while(continueSearching && index < installedBundles.length) { + final Bundle installedBundle = installedBundles[index++]; + final File installedBundleFile; + + try { + installedBundleFile = new File(new URL(installedBundle.getLocation()).getFile()); + + continueSearching = !bundlePath.equals(installedBundleFile.toPath().toAbsolutePath()); + + if(!continueSearching) { + uninstallBundle(installedBundle); + } + } catch (MalformedURLException e) { + LOGGER.log(Level.FINE, "Can not create the URL of the bundle: " + bundleFile.getName(), e); + } + } + } + + /** + * Return the list of installed services which are from the given {@code serviceType} class. + * @param The type of service. + * @param serviceType The class of service to look for. + * @return the list of installed services or an empty list if there is no service corresponding to the given class. + */ + public List getInstalledServices(Class serviceType) { + final List services = new ArrayList<>(); + + try { + Collection> references = + osgiFramework.getBundleContext().getServiceReferences(serviceType, "(objectClass=" + serviceType.getName() + ")"); + + references.stream().forEach(ref -> services.add(osgiFramework.getBundleContext().getService(ref))); + } catch (InvalidSyntaxException e) { + LOGGER.log(WARNING, "Can not list all installed service of type " + serviceType.getName()); + } + + return services; + } + + /** + * Get the list of {@link InstalledPlugin} of the given type. + * @param pluginType The type of the plugin to list. + * @param The type of the plugins. + * @return The list containing all installed plugins of the desired type. + */ + public List getInstalledPlugins(Class pluginType) { + final List installedPlugins = new ArrayList<>(); + + try { + final Collection> services = + osgiFramework.getBundleContext().getServiceReferences(pluginType, "(objectClass=" + pluginType.getName() + ")"); + installedPlugins.addAll( + services.stream() + .map(service -> { + final Bundle bundle = service.getBundle(); + return new InstalledPlugin(bundle.getHeaders().get("Bundle-Name"), bundle.getVersion().toString()); + }) + .sorted((plugin1, plugin2) -> plugin1.getName().compareTo(plugin2.getName())) + .collect(Collectors.toList())); + } catch (InvalidSyntaxException e) { + LOGGER.log(WARNING, "Can not list all installed plugin of type " + pluginType.getName()); + } + + return installedPlugins; + } + + /** + * Get the list of active plugins. + * @return The list of active plugins. + */ + public List getActivePlugins() { + return Arrays.stream(this.osgiFramework.getBundleContext().getBundles()) + .filter(bundle -> !SYSTEM_BUNDLE_LOCATION.equals(bundle.getLocation())) + .map(bundle -> { + try { + return new File(new URL(bundle.getLocation()).getFile()); + } catch (MalformedURLException e) { + LOGGER.log(Level.SEVERE, "Can not determine plugin location", e); + return null; + } + }) + .filter(file -> file != null) + .collect(Collectors.toList()); + } + + public Object getPresentationProperty(String property) { + Object value = null; + + if(Presentations.getCurrentDisplayedPresentation() != null) { + if(PRESENTATION_FOLDER.equals(property)) { + value = Presentations.getCurrentDisplayedPresentation().getWorkingDirectory(); + } + else if(PRESENTATION_RESOURCES_FOLDER.equals(property)) { + value = Presentations.getCurrentDisplayedPresentation().getTemplateConfiguration().getResourcesDirectory(); + } + } + + return value; + } + + /** + * Checks if a given plugin is the most recent compared to installed plugin. If the plugin version is strictly + * greater than the first plugin's version found then it is considered as the most recent plugin. + * @param plugin The plugin to check. + * @return {@code true} if the plugin is the most recent, {@code false} otherwise. + */ + protected boolean isPluginMostRecent(final Bundle plugin) { + boolean isMostRecent; + + final Version installedPluginVersion = Arrays.stream(osgiFramework.getBundleContext().getBundles()) + .filter(installedPlugin -> { + boolean isSameName = installedPlugin.getSymbolicName().equals(plugin.getSymbolicName()); + + return isSameName; + }) + .map(Bundle::getVersion) + .findFirst() + .orElse(null); + + if(installedPluginVersion != null) { + isMostRecent = plugin.getVersion().compareTo(installedPluginVersion) >= 0; + } else { + isMostRecent = true; + } + + return isMostRecent; + } + + /** + * Checks if a given plugin is already installed in another version in the OSGi framework. The plugin's match is + * performed on the {@link Bundle#getSymbolicName() symbolic name} of the plugin. + * @param plugin The plugin to check. + * @return {@code true} if the plugin is installed in another version, {@code false} otherwise. + */ + protected boolean isPluginInAnotherVersionInstalled(final Bundle plugin) { + return Arrays.stream(osgiFramework.getBundleContext().getBundles()) + .anyMatch(installedPlugin -> { + boolean isSameName = installedPlugin.getSymbolicName().equals(plugin.getSymbolicName()); + boolean isNotSameVersion = !installedPlugin.getVersion().equals(plugin.getVersion()); + + return isSameName && isNotSameVersion; + }); + } + + /** + * Get the other version of the given plugin. The plugin's match is performed on the + * {@link Bundle#getSymbolicName() symbolic name} of the plugin. + * @param plugin The plugin to check. + * @return The plugin in the other version of the given plugin, {@code null} if not found. + */ + protected Bundle getPluginInAnotherVersion(final Bundle plugin) { + return Arrays.stream(osgiFramework.getBundleContext().getBundles()) + .filter(installedPlugin -> { + boolean isSameName = installedPlugin.getSymbolicName().equals(plugin.getSymbolicName()); + boolean isNotSameVersion = !installedPlugin.getVersion().equals(plugin.getVersion()); + + return isSameName && isNotSameVersion; + }) + .findFirst() + .orElse(null); + } + + /** + * Check if the given plugin is active or not. A plugin is considered active if it's state is equal to {@link Bundle#ACTIVE} + * or {@link Bundle#STARTING}. + * @param plugin The plugin to check. + * @return {@code true} if the plugin is active, {@code false} otherwise. + */ + protected boolean isPluginActive(final Bundle plugin) { + return Bundle.ACTIVE == plugin.getState() || Bundle.STARTING == plugin.getState(); + } + + /** + * Check if the given plugin is inactive or not. + * @param plugin The plugin to check. + * @return {@code true} if the plugin is inactive, {@code false} otherwise. + * @see #isPluginActive(Bundle) + */ + protected boolean isPluginInactive(final Bundle plugin) { + return !isPluginActive(plugin); + } +} diff --git a/slideshowfx-osgi/src/test/java/com/twasyl/slideshowfx/osgi/OSGiManagerTest.java b/slideshowfx-osgi/src/test/java/com/twasyl/slideshowfx/osgi/OSGiManagerTest.java new file mode 100755 index 00000000..8597e056 --- /dev/null +++ b/slideshowfx-osgi/src/test/java/com/twasyl/slideshowfx/osgi/OSGiManagerTest.java @@ -0,0 +1,211 @@ +package com.twasyl.slideshowfx.osgi; + +import com.twasyl.slideshowfx.utils.io.CopyFileVisitor; +import com.twasyl.slideshowfx.utils.io.IOUtils; +import org.junit.jupiter.api.*; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; +import static org.osgi.framework.Constants.SYSTEM_BUNDLE_LOCATION; + +/** + * This class tests the {@link OSGiManager} classes + * + * @author Thierry Wasylczenko + * @version 1.0 + * @since SlideshowFX 1.0 + */ +public class OSGiManagerTest { + + private static OSGiManager osgiManager; + private static final File ROOT_OSGI = new File("build/resources/test/com/twasyl/slideshowfx/osgi"); + private static final File PLUGINS_DIR = new File(ROOT_OSGI, "testPlugins"); + private static final File OSGI_CACHE_DIR = new File(ROOT_OSGI, "felix-cache"); + private static final File PLUGIN_1_0 = new File(PLUGINS_DIR, "plugin-1.0.jar"); + private static final File PLUGIN_1_1 = new File(PLUGINS_DIR, "plugin-1.1.jar"); + private static final File PLUGIN_1_2 = new File(PLUGINS_DIR, "plugin-1.2.jar"); + private static final File PLUGIN2_3_0 = new File(PLUGINS_DIR, "plugin2-3.0.jar"); + + @BeforeAll + public static void setUp() { + osgiManager = new OSGiManager(); + osgiManager.pluginsDirectory = PLUGINS_DIR; + } + + @AfterAll + public static void tearDown() throws IOException { + IOUtils.deleteDirectory(OSGI_CACHE_DIR); + } + + @BeforeEach + public void before() throws URISyntaxException, IOException { + final Path source = new File("src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins").toPath().toAbsolutePath(); + Files.walkFileTree(source, new CopyFileVisitor(ROOT_OSGI.toPath().toAbsolutePath(), source)); + + osgiManager.osgiCache = new File(OSGI_CACHE_DIR, System.currentTimeMillis() + ""); + osgiManager.start(); + } + + @AfterEach + public void after() { + osgiManager.stop(); + } + + @Test + public void isMostRecent() throws BundleException { + final Bundle bundle = osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_0.getAbsolutePath())); + + assertTrue(osgiManager.isPluginMostRecent(bundle)); + } + + @Test + public void isNotMostRecent() throws BundleException { + osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_2.getAbsolutePath())); + final Bundle bundle = osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_0.getAbsolutePath())); + + assertFalse(osgiManager.isPluginMostRecent(bundle)); + } + + @Test + public void isActive() throws BundleException { + final Bundle bundle = osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s",PLUGIN_1_0.getAbsolutePath())); + bundle.start(); + + assertTrue(osgiManager.isPluginActive(bundle)); + } + + @Test + public void isNotActive() throws BundleException { + final Bundle bundle = osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_0.getAbsolutePath())); + + assertFalse(osgiManager.isPluginActive(bundle)); + } + + @Test + public void otherVersionInstalled() throws BundleException { + osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_0.getAbsolutePath())); + final Bundle bundle = osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_1.getAbsolutePath())); + + assertTrue(osgiManager.isPluginInAnotherVersionInstalled(bundle)); + } + + @Test + public void noOtherVersionInstalled() throws BundleException { + final Bundle bundle = osgiManager.osgiFramework.getBundleContext().installBundle(String.format("file:%1$s", PLUGIN_1_0.getAbsolutePath())); + + assertFalse(osgiManager.isPluginInAnotherVersionInstalled(bundle)); + } + + @Test + public void pluginIsDeployed() throws IOException { + osgiManager.deployBundle(PLUGIN_1_0); + + // Expected number of plugins is 2 because Felix is a bundle itself + assertEquals(2, osgiManager.osgiFramework.getBundleContext().getBundles().length); + } + + @Test + public void pluginIsDeployedBecauseMostRecent() throws IOException { + osgiManager.deployBundle(PLUGIN_1_0); + osgiManager.deployBundle(PLUGIN_1_1); + osgiManager.deployBundle(PLUGIN_1_2); + + // Expected number of plugins is 2 because Felix is a bundle itself + final Bundle[] bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(2, bundles.length); + } + + @Test + public void oldVersionsAreUninstalled() throws IOException { + osgiManager.deployBundle(PLUGIN_1_0); + osgiManager.deployBundle(PLUGIN_1_1); + osgiManager.deployBundle(PLUGIN_1_2); + + final Bundle[] bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + final Bundle installedBundle = Arrays.stream(bundles) + .filter(bundle -> "com.twasyl.slideshowfx.plugin".equals(bundle.getSymbolicName())) + .findFirst() + .orElse(null); + + assertNotNull(installedBundle); + assertEquals("1.2.0", installedBundle.getVersion().toString()); + } + + @Test + public void pluginIsNotDeployedBecauseTooOld() throws IOException { + osgiManager.deployBundle(PLUGIN_1_2); + osgiManager.deployBundle(PLUGIN_1_0); + osgiManager.deployBundle(PLUGIN_1_1); + + // Expected number of plugins is 2 because Felix is a bundle itself + final Bundle[] bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(2, bundles.length); + + final Bundle installedBundle = Arrays.stream(bundles) + .filter(bundle -> "com.twasyl.slideshowfx.plugin".equals(bundle.getSymbolicName())) + .findFirst() + .orElse(null); + + assertNotNull(installedBundle); + assertEquals("1.2.0", installedBundle.getVersion().toString()); + } + + @Test + public void undeployBundle() throws IOException, BundleException { + osgiManager.deployBundle(PLUGIN_1_2); + + // Expected number of plugins is 2 because Felix is a bundle itself + Bundle[] bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(2, bundles.length); + + osgiManager.uninstallBundle(PLUGIN_1_2); + + bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(1, bundles.length); + } + + @Test + public void undeployBundleWhenOtherDeployed() throws IOException, BundleException { + osgiManager.deployBundle(PLUGIN2_3_0); + osgiManager.deployBundle(PLUGIN_1_2); + + // Expected number of plugins is 3 because Felix is a bundle itself + Bundle[] bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(3, bundles.length); + + osgiManager.uninstallBundle(PLUGIN_1_2); + + bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(2, bundles.length); + + final Bundle installedBundle = Arrays.stream(bundles) + .filter(bundle -> "com.twasyl.slideshowfx.plugin2".equals(bundle.getSymbolicName())) + .findFirst() + .orElse(null); + + assertNotNull(installedBundle); + } + + @Test + public void startAndDeploy() { + osgiManager.stop(); + osgiManager.startAndDeploy(); + assertNotNull(osgiManager.osgiFramework); + + // Expected number of plugins is 3 because Felix is a bundle itself + Bundle[] bundles = osgiManager.osgiFramework.getBundleContext().getBundles(); + assertEquals(3, bundles.length); + + final long activePlugins = Arrays.stream(bundles) + .filter(bundle -> !SYSTEM_BUNDLE_LOCATION.equals(bundle.getLocation()) && osgiManager.isPluginActive(bundle)).count(); + assertEquals(2, activePlugins); + } +} diff --git a/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.0.jar b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.0.jar new file mode 100644 index 00000000..82884255 Binary files /dev/null and b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.0.jar differ diff --git a/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.1.jar b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.1.jar new file mode 100644 index 00000000..d2513145 Binary files /dev/null and b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.1.jar differ diff --git a/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.2.jar b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.2.jar new file mode 100644 index 00000000..81f49237 Binary files /dev/null and b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin-1.2.jar differ diff --git a/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin2-3.0.jar b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin2-3.0.jar new file mode 100644 index 00000000..1428c923 Binary files /dev/null and b/slideshowfx-osgi/src/test/resources/com/twasyl/slideshowfx/osgi/testPlugins/plugin2-3.0.jar differ diff --git a/slideshowfx-plugin/build.gradle b/slideshowfx-plugin/build.gradle new file mode 100644 index 00000000..39a8d901 --- /dev/null +++ b/slideshowfx-plugin/build.gradle @@ -0,0 +1,4 @@ +description = "Module defining a markup" +version = '1.0' + +apply plugin: 'java' \ No newline at end of file diff --git a/SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/AbstractPlugin.java b/slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/AbstractPlugin.java similarity index 100% rename from SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/AbstractPlugin.java rename to slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/AbstractPlugin.java diff --git a/SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IConfigurable.java b/slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IConfigurable.java similarity index 100% rename from SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IConfigurable.java rename to slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IConfigurable.java diff --git a/SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPlugin.java b/slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPlugin.java similarity index 100% rename from SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPlugin.java rename to slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPlugin.java diff --git a/SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPluginOptions.java b/slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPluginOptions.java similarity index 100% rename from SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPluginOptions.java rename to slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/IPluginOptions.java diff --git a/SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/InstalledPlugin.java b/slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/InstalledPlugin.java similarity index 100% rename from SlideshowFX-plugin/src/main/java/com/twasyl/slideshowfx/plugin/InstalledPlugin.java rename to slideshowfx-plugin/src/main/java/com/twasyl/slideshowfx/plugin/InstalledPlugin.java diff --git a/slideshowfx-quiz-extension/build.gradle b/slideshowfx-quiz-extension/build.gradle new file mode 100644 index 00000000..1338bc4a --- /dev/null +++ b/slideshowfx-quiz-extension/build.gradle @@ -0,0 +1,26 @@ +description = 'Extension allowing to insert quizs inside a SlideshowFX presentation' +version = '1.1' + +apply plugin: 'java-library' + +dependencies { + api project(':slideshowfx-content-extension') + implementation project(':slideshowfx-global-configuration') + implementation project(':slideshowfx-icons') + api project(':slideshowfx-markup') + api project(':slideshowfx-plugin') + api project(':slideshowfx-server') + api project(':slideshowfx-ui-controls') +} + +ext.isPlugin = true +ext.isContentExtension = true +ext.bundle = [ + name : 'SlideshowFX quiz extension', + symbolicName : 'com.twasyl.slideshowfx.content.extension.quiz', + description : 'Support for inserting quiz in slides', + activator : 'com.twasyl.slideshowfx.content.extension.quiz.activator.QuizContentExtensionActivator', + exportPackage : 'com.twasyl.slideshowfx.content.extension.quiz.controllers,com.twasyl.slideshowfx.content.extension.quiz,com.twasyl.slideshowfx.content.extension.quiz.activator', + setupWizardLabel : 'Quiz', + setupWizardIconName: 'QUESTION' +] \ No newline at end of file diff --git a/SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/QuizContentExtension.java b/slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/QuizContentExtension.java similarity index 64% rename from SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/QuizContentExtension.java rename to slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/QuizContentExtension.java index 98ace5c5..eaf43ba8 100644 --- a/SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/QuizContentExtension.java +++ b/slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/QuizContentExtension.java @@ -1,11 +1,10 @@ package com.twasyl.slideshowfx.content.extension.quiz; import com.twasyl.slideshowfx.content.extension.AbstractContentExtension; -import com.twasyl.slideshowfx.content.extension.ResourceType; import com.twasyl.slideshowfx.content.extension.quiz.controllers.QuizContentExtensionController; import com.twasyl.slideshowfx.markup.IMarkup; import com.twasyl.slideshowfx.server.beans.quiz.Quiz; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.fxml.FXMLLoader; import javafx.scene.layout.Pane; @@ -14,7 +13,11 @@ import java.util.logging.Level; import java.util.logging.Logger; +import static com.twasyl.slideshowfx.content.extension.ResourceType.CSS_FILE; +import static com.twasyl.slideshowfx.content.extension.ResourceType.JAVASCRIPT_FILE; import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.getDefaultCharset; +import static com.twasyl.slideshowfx.icons.FontAwesome.*; +import static com.twasyl.slideshowfx.icons.Icon.QUESTION; /** * The {@link QuizContentExtension} extends the {@link AbstractContentExtension}. It allows to build a content containing @@ -23,7 +26,7 @@ * This extension supports HTML markup language. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class QuizContentExtension extends AbstractContentExtension { @@ -32,15 +35,14 @@ public class QuizContentExtension extends AbstractContentExtension { private QuizContentExtensionController controller; public QuizContentExtension() { - super("QUIZ", QuizContentExtension.class.getResource("/com/twasyl/slideshowfx/content/extension/quiz/resources/quiz.zip"), - FontAwesomeIcon.QUESTION, + super("QUIZ", null, + QUESTION, "Insert a quiz", "Insert a quiz"); - final String baseURL = "quiz/"; - // Add URL - this.putResource(ResourceType.CSS_FILE, baseURL.concat("font-awesome-4.6.3/css/font-awesome.min.css")); + this.putResource(CSS_FILE, String.format("quiz/font-awesome/%s/css/%s", getFontAwesomeVersion(), getFontAwesomeCSSFilename()), getFontAwesomeCSSFile()); + this.putResource(JAVASCRIPT_FILE, String.format("quiz/font-awesome/%s/js/%s", getFontAwesomeVersion(), getFontAwesomeJSFilename()), getFontAwesomeJSFile()); } @Override @@ -68,11 +70,16 @@ public String buildContentString(IMarkup markup) { public String buildDefaultContentString() { final Quiz quiz = controller.getQuiz(); + final String encodedQuiz = Base64.getEncoder().encodeToString(quiz.toJSONString().getBytes(getDefaultCharset())); + final StringBuilder builder = new StringBuilder("
\n"); builder.append("\t\n") - .append("\t\t") - .append("\n\t\t ") + .append("\t\t") + .append("") + .append("") + .append("\n\t\t ") .append(quiz.getQuestion().getText()).append("\n\t\n"); builder.append("\t
    "); @@ -84,4 +91,9 @@ public String buildDefaultContentString() { return builder.toString(); } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + return this.controller.areInputsValid(); + } } diff --git a/SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/activator/QuizContentExtensionActivator.java b/slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/activator/QuizContentExtensionActivator.java similarity index 100% rename from SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/activator/QuizContentExtensionActivator.java rename to slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/activator/QuizContentExtensionActivator.java diff --git a/SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/controllers/QuizContentExtensionController.java b/slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/controllers/QuizContentExtensionController.java old mode 100644 new mode 100755 similarity index 75% rename from SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/controllers/QuizContentExtensionController.java rename to slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/controllers/QuizContentExtensionController.java index 436cdcdf..fcb6518c --- a/SlideshowFX-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/controllers/QuizContentExtensionController.java +++ b/slideshowfx-quiz-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quiz/controllers/QuizContentExtensionController.java @@ -4,40 +4,53 @@ import com.twasyl.slideshowfx.server.beans.quiz.Answer; import com.twasyl.slideshowfx.server.beans.quiz.Question; import com.twasyl.slideshowfx.server.beans.quiz.Quiz; +import com.twasyl.slideshowfx.ui.controls.ZoomTextArea; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.geometry.Pos; -import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import java.net.URL; import java.util.ResourceBundle; +import static com.twasyl.slideshowfx.ui.controls.validators.Validators.isNotEmpty; + /** * This class is the controller used by the {@code QuoteContentExtension.fxml} file. + * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class QuizContentExtensionController extends AbstractContentExtensionController { - @FXML private VBox answersBox; - @FXML private TextArea question; - @FXML private Button addAnswer = new Button(); + @FXML + private VBox answersBox; + @FXML + private ZoomTextArea question; + @FXML + private Button addAnswer = new Button(); private final ObjectProperty quiz = new SimpleObjectProperty<>(); /** - * Add an answer to this {@link #quiz}. This method creates an {@link com.twasyl.slideshowfx.server.beans.quiz.Answer} + * Add an answer to this {@link #quiz}. This method creates an {@link Answer} * object and binds it to the elements that are used to specify the text of it and if it is considered as a right * answer. * The method also updates this panel with elements used to define the answer. The answer is also added to this * quiz. */ - @FXML private void addAnswer(final ActionEvent event) { + @FXML + private void addAnswer(final ActionEvent event) { this.addAnswer(); } @@ -58,7 +71,7 @@ private void addAnswer() { isCorrect.setTooltip(new Tooltip("Check if this answer is considered as a correct answer")); isCorrect.selectedProperty().bindBidirectional(answer.correctProperty()); - if(this.addAnswer.getParent() != null) { + if (this.addAnswer.getParent() != null) { super.executeUnderFXThread(() -> ((HBox) this.addAnswer.getParent()).getChildren().remove(this.addAnswer)); } @@ -70,6 +83,7 @@ private void addAnswer() { @Override public void initialize(URL location, ResourceBundle resources) { + this.question.setValidator(isNotEmpty()); this.initializeQuiz(); this.addAnswer(); } @@ -92,14 +106,28 @@ private void initializeQuiz() { /** * Get the {@link Quiz} associated to this controller. The quiz is fully initialized and doesn't need further configuration. * The quiz is never null. + * * @return The property containing the quiz. */ - public ObjectProperty quizProperty() { return quiz; } + public ObjectProperty quizProperty() { + return quiz; + } /** * Get the {@link Quiz} associated to this controller. The quiz is fully initialized and doesn't need further configuration. * The quiz is never null. + * * @return The quiz itself. */ - public Quiz getQuiz() { return quiz.get(); } + public Quiz getQuiz() { + return quiz.get(); + } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + final ReadOnlyBooleanWrapper property = new ReadOnlyBooleanWrapper(); + property.bind(this.question.validProperty()); + + return property; + } } diff --git a/SlideshowFX-quiz-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quiz/fxml/QuizContentExtension.fxml b/slideshowfx-quiz-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quiz/fxml/QuizContentExtension.fxml similarity index 52% rename from SlideshowFX-quiz-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quiz/fxml/QuizContentExtension.fxml rename to slideshowfx-quiz-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quiz/fxml/QuizContentExtension.fxml index fbcb062d..47d2b1ca 100644 --- a/SlideshowFX-quiz-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quiz/fxml/QuizContentExtension.fxml +++ b/slideshowfx-quiz-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quiz/fxml/QuizContentExtension.fxml @@ -1,36 +1,40 @@ - + + - + + + - + - + - + - + @@ -38,7 +42,7 @@ - + diff --git a/slideshowfx-quote-extension/build.gradle b/slideshowfx-quote-extension/build.gradle new file mode 100644 index 00000000..2cd6e96a --- /dev/null +++ b/slideshowfx-quote-extension/build.gradle @@ -0,0 +1,24 @@ +description = 'Extension allowing to insert quotes inside a SlideshowFX presentation' +version = '1.1' + +apply plugin: 'java-library' + +dependencies { + api project(':slideshowfx-content-extension') + implementation project(':slideshowfx-icons') + api project(':slideshowfx-markup') + api project(':slideshowfx-plugin') + api project(':slideshowfx-ui-controls') +} + +ext.isPlugin = true +ext.isContentExtension = true +ext.bundle = [ + name : 'SlideshowFX quote extension', + symbolicName : 'com.twasyl.slideshowfx.content.extension.quote', + description : 'Support for inserting quote in slides', + activator : 'com.twasyl.slideshowfx.content.extension.quote.activator.QuoteContentExtensionActivator', + exportPackage : 'com.twasyl.slideshowfx.content.extension.quote.controllers,com.twasyl.slideshowfx.content.extension.quote,com.twasyl.slideshowfx.content.extension.quote.activator', + setupWizardLabel : 'Quote', + setupWizardIconName: 'QUOTE_LEFT' +] \ No newline at end of file diff --git a/SlideshowFX-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/QuoteContentExtension.java b/slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/QuoteContentExtension.java old mode 100644 new mode 100755 similarity index 87% rename from SlideshowFX-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/QuoteContentExtension.java rename to slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/QuoteContentExtension.java index 7b51af79..1952f114 --- a/SlideshowFX-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/QuoteContentExtension.java +++ b/slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/QuoteContentExtension.java @@ -3,7 +3,7 @@ import com.twasyl.slideshowfx.content.extension.AbstractContentExtension; import com.twasyl.slideshowfx.content.extension.quote.controllers.QuoteContentExtensionController; import com.twasyl.slideshowfx.markup.IMarkup; -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.fxml.FXMLLoader; import javafx.scene.layout.Pane; @@ -11,6 +11,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import static com.twasyl.slideshowfx.icons.Icon.QUOTE_LEFT; + /** * The QuoteContentExtension extends the AbstractContentExtension. It allows to build a content containing quote to insert * inside a SlideshowFX presentation. @@ -18,7 +20,7 @@ * This extension supports HTML and Textile markup languages. * * @author Thierry Wasylczenko - * @version 1.0 + * @version 1.1 * @since SlideshowFX 1.0 */ public class QuoteContentExtension extends AbstractContentExtension { @@ -28,7 +30,7 @@ public class QuoteContentExtension extends AbstractContentExtension { public QuoteContentExtension() { super("QUOTE", null, - FontAwesomeIcon.QUOTE_LEFT, + QUOTE_LEFT, "Insert a quote", "Insert a quote"); } @@ -53,9 +55,9 @@ public Pane getUI() { public String buildContentString(IMarkup markup) { final StringBuilder builder = new StringBuilder(); - if(markup == null || "HTML".equals(markup.getCode())) { + if (markup == null || "HTML".equals(markup.getCode())) { builder.append(this.buildDefaultContentString()); - } else if("TEXTILE".equals(markup.getCode())) { + } else if ("TEXTILE".equals(markup.getCode())) { builder.append("bq.. ") .append(this.controller.getQuote()) .append("\np{text-align: right; font-weight: bold; font-style: italic;}. ") @@ -79,4 +81,9 @@ public String buildDefaultContentString() { return builder.toString(); } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + return this.controller.areInputsValid(); + } } diff --git a/SlideshowFX-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/activator/QuoteContentExtensionActivator.java b/slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/activator/QuoteContentExtensionActivator.java similarity index 100% rename from SlideshowFX-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/activator/QuoteContentExtensionActivator.java rename to slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/activator/QuoteContentExtensionActivator.java diff --git a/slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/controllers/QuoteContentExtensionController.java b/slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/controllers/QuoteContentExtensionController.java new file mode 100755 index 00000000..4e1efd81 --- /dev/null +++ b/slideshowfx-quote-extension/src/main/java/com/twasyl/slideshowfx/content/extension/quote/controllers/QuoteContentExtensionController.java @@ -0,0 +1,59 @@ +package com.twasyl.slideshowfx.content.extension.quote.controllers; + +import com.twasyl.slideshowfx.content.extension.AbstractContentExtensionController; +import com.twasyl.slideshowfx.ui.controls.ZoomTextArea; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; + +import java.net.URL; +import java.util.ResourceBundle; + +import static com.twasyl.slideshowfx.ui.controls.validators.Validators.isNotEmpty; + +/** + * This class is the controller used by the {@code QuoteContentExtension.fxml} file. + * + * @author Thierry Wasylczenko + * @version 1.1 + * @since SlideshowFX 1.0 + */ +public class QuoteContentExtensionController extends AbstractContentExtensionController { + + @FXML + private ZoomTextArea quote; + @FXML + private TextField author; + + /** + * Get the quote entered in the UI. + * + * @return The quote entered in the UI. + */ + public String getQuote() { + return this.quote.getText(); + } + + /** + * Get the author of the quote inserted in the UI. + * + * @return The author inserted in the UI. + */ + public String getAuthor() { + return this.author.getText(); + } + + @Override + public ReadOnlyBooleanProperty areInputsValid() { + final ReadOnlyBooleanWrapper property = new ReadOnlyBooleanWrapper(); + property.bind(this.quote.validProperty()); + + return property; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + this.quote.setValidator(isNotEmpty()); + } +} diff --git a/SlideshowFX-quote-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quote/fxml/QuoteContentExtension.fxml b/slideshowfx-quote-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quote/fxml/QuoteContentExtension.fxml similarity index 79% rename from SlideshowFX-quote-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quote/fxml/QuoteContentExtension.fxml rename to slideshowfx-quote-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quote/fxml/QuoteContentExtension.fxml index d573e866..8dfcb204 100644 --- a/SlideshowFX-quote-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quote/fxml/QuoteContentExtension.fxml +++ b/slideshowfx-quote-extension/src/main/resources/com/twasyl/slideshowfx/content/extension/quote/fxml/QuoteContentExtension.fxml @@ -1,7 +1,7 @@ + - @@ -12,6 +12,6 @@ -