package com.twasyl.slideshowfx.controls;
import com.twasyl.slideshowfx.utils.PlatformHelper;
import com.twasyl.slideshowfx.utils.ResourceHelper;
import com.twasyl.slideshowfx.utils.ZipUtils;
import com.twasyl.slideshowfx.utils.keys.KeyEventUtils;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebView;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.twasyl.slideshowfx.global.configuration.GlobalConfiguration.getDefaultCharset;
/**
* This control allows to define the content for a slide. It provides helper methods for inserting the current slide
* content in the editor as well as getting it.
*
* @author Thierry Wasylczenko
* @version 1.1
* @since SlideshowFX 1.0
*/
public class SlideContentEditor extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(SlideContentEditor.class.getName());
private final WebView browser = new WebView();
public SlideContentEditor() {
this.browser.getEngine().load(this.prepareAndGetEditorPageURI());
this.browser.setOnKeyPressed(event -> {
final boolean isShortcutDown = event.isShortcutDown();
if(isShortcutDown) {
if(KeyEventUtils.isShortcutSequence("A", event)) SlideContentEditor.this.selectAll();
}
});
this.registerEvent(ScrollEvent.SCROLL, event -> {
if(event.isShortcutDown()) {
this.browser.getEngine().executeScript(String.format("changeFontSize(%1$s);", event.getDeltaY()));
}
});
this.setCenter(this.browser);
}
/**
* Prepare the HTML page that is used to define and edit slides' content and return the {@link java.net.URI} of the
* page in order to be loaded by a {@link WebView}.
* @return The {@link java.net.URI} of the page to load.
*/
private String prepareAndGetEditorPageURI() {
final File tempDirectory = new File(System.getProperty("java.io.tmpdir"));
final File editorDir = new File(tempDirectory, "sfx-slide-content-editor");
final File editorFile = new File(editorDir, "ace-file-editor.html");
final String uri = editorFile.toURI().toASCIIString();
if(!editorFile.exists()) {
try(final InputStream editorZip = ResourceHelper.getInputStream("/com/twasyl/slideshowfx/sfx-slide-content-editor.zip")) {
ZipUtils.unzip(editorZip, tempDirectory);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Can not extract the slide content editor ZIP", e);
}
}
return uri;
}
/**
* This method retrieves the content of the Node allowing to define the content of the slide.
* @return The text contained in the Node for defining content of the slide.
*/
public String getContentEditorValue() {
final String valueAsBase64 = (String) this.browser.getEngine().executeScript("getContent();");
final byte[] valueAsBytes = Base64.getDecoder().decode(valueAsBase64);
String value = new String(valueAsBytes, getDefaultCharset());
return value;
}
/**
* This method retrieves the selected content of the Node allowing to define the content of the slide.
* @return The text contained in the Node for defining content of the slide.
*/
public String getSelectedContentEditorValue() {
final String valueAsBase64 = (String) this.browser.getEngine().executeScript("getSelectedContent();");
final byte[] valueAsBytes = Base64.getDecoder().decode(valueAsBase64);
String value = new String(valueAsBytes, getDefaultCharset());
return value;
}
/**
* Set the value for this content editor. This method doesn't append the given {@code value} to the current one
* present in this editor. In order to append the value use {@link #appendContentEditorValue(String)}.
* @param value The new value of this editor
*/
public void setContentEditorValue(final String value) {
final String encodedValue = Base64.getEncoder().encodeToString(value.getBytes(getDefaultCharset()));
this.browser.getEngine().executeScript(String.format("setContent('%1$s');", encodedValue));
}
/**
* Append the given value to this content editor. The current caret position is taken in consideration in order to
* append the value.
* @param value The value to append to the content editor.
*/
public void appendContentEditorValue(final String value) {
final String encodedValue = Base64.getEncoder().encodeToString(value.getBytes(getDefaultCharset()));
this.browser.getEngine().executeScript(String.format("appendContent('%1$s');", encodedValue));
}
/**
* Select all text that is currently in the editor.
*/
public void selectAll() {
this.browser.getEngine().executeScript("selectAll();");
}
/**
* Removes the selected text in the editor.
*/
public void removeSelection() {
this.browser.getEngine().executeScript("removeSelection();");
}
/**
* Set the mode for the content editor. If {@code null} or an empty string is passed, plain text is set as mode.
* @param mode The mode for the content editor.
*/
public void setMode(String mode) {
if(mode == null || mode.isEmpty()) {
this.browser.getEngine().executeScript("setMode('ace/mode/plain_text');");
} else {
this.browser.getEngine().executeScript(String.format("setMode('%1$s');", mode));
}
}
/**
* Make the editor requests the focus in the application. The text that is already present in the editor will be
* fully selected and the editor will ask for the focus.
*/
@Override
public void requestFocus() {
PlatformHelper.run(() -> {
this.browser.requestFocus();
this.browser.getEngine().executeScript("selectAll();");
this.browser.getEngine().executeScript("requestEditorFocus();");
});
}
/**
* Register an handler to this browser.
* @param eventType The type of event to register.
* @param handler The handler of the event.
*/
public <T extends Event> void registerEvent(EventType<T> eventType, EventHandler<? super T> handler) {
this.browser.addEventHandler(eventType, handler);
}
}