All Downloads are FREE. Search and download functionalities are using the official Maven repository.

application.ui.preview.PreviewPefController Maven / Gradle / Ivy

package application.ui.preview;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

import org.daisy.braille.utils.pef.PEFBook;
import org.daisy.dotify.studio.api.DocumentPosition;
import org.daisy.dotify.studio.api.ExportAction;
import org.daisy.dotify.studio.api.OpenableEditor;
import org.daisy.dotify.studio.api.SearchCapabilities;
import org.daisy.dotify.studio.api.SearchOptions;
import org.daisy.streamline.api.media.FileDetails;
import org.daisy.streamline.api.validity.ValidationReport;

import application.l10n.Messages;
import application.ui.preview.server.Start;
import application.ui.preview.server.StartupDetails;
import application.ui.preview.server.preview.stax.BookReaderResult;
import application.ui.preview.server.preview.stax.StaxPreviewParser;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableObjectValue;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.BorderPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;

/**
 * Provides a preview controller.
 * @author Joel Håkansson
 *
 */
public class PreviewPefController extends BorderPane implements OpenableEditor {
	private static final Logger logger = Logger.getLogger(PreviewPefController.class.getCanonicalName());
	private static final ReadOnlyObjectProperty SEARCH_CAPABILITIES = new SimpleObjectProperty<>(
			new SearchCapabilities.Builder()
			.direction(true)
			.matchCase(true)
			.wrap(true)
			.find(true)
			.replace(false)
			.build()
	);
	@FXML WebView browser;
	private Start start;
	private boolean closing;
	private EmbossView embossView;
	private final ReadOnlyBooleanProperty canEmbossProperty;
	private final ReadOnlyBooleanProperty canSaveProperty;
	private final ObservableBooleanValue canSaveAsProperty;
	private StringProperty urlProperty;
	private ObjectProperty fileDetails = new SimpleObjectProperty<>(FileDetailsCatalog.PEF_FORMAT);
	private ObjectProperty> validationReport = new SimpleObjectProperty<>(Optional.empty());
	private String pageUrl;

	/**
	 * Creates a new preview controller.
	 */
	public PreviewPefController() {
		try {
			FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("Preview.fxml"), Messages.getBundle());
			fxmlLoader.setRoot(this);
			fxmlLoader.setController(this);
			fxmlLoader.load();
		} catch (IOException e) {
			logger.log(Level.WARNING, "Failed to load view", e);
		}

        WebEngine webEngine = browser.getEngine();
        browser.setOnDragOver(event->event.consume());
        webEngine.setCreatePopupHandler(p-> {
                Stage stage = new Stage(StageStyle.UTILITY);
                WebView wv2 = new WebView();
                stage.setScene(new Scene(wv2));
                stage.show();
                return wv2.getEngine();
            }
        );
		closing = false;
		canEmbossProperty = BooleanProperty.readOnlyBooleanProperty(new SimpleBooleanProperty(true));
		canSaveProperty = BooleanProperty.readOnlyBooleanProperty(new SimpleBooleanProperty(false));
		canSaveAsProperty = new SimpleBooleanProperty(true);
		urlProperty = new SimpleStringProperty();
	}

	public static boolean supportsFormat(FileDetails format) {
		return FormatChecker.isPEF(format);
	}

    class PefDocumentWatcher extends DocumentWatcher {
    	PefDocumentWatcher(File pef) {
    		super(pef);
    	}

		@Override
		boolean shouldMonitor() {
			return super.shouldMonitor() && !closing;
		}

		@Override
		void performAction() {
			Platform.runLater(()->reload());
		}

    }

	/**
	 * Starts a new preview server.
	 * @param file the file
	 * @return returns a thread that watches for changes in the pef file
	 */
    @Override
	public Consumer open(File file) {
		Thread pwt = null;
		if (file!=null) {
			PefDocumentWatcher pefWatcher = new PefDocumentWatcher(file);
    		pwt = new Thread(pefWatcher);
    		pwt.setDaemon(true);
    		pwt.start();
		}
		final Thread pefWatcherThread = pwt;
		Task startServer = new Task() {

			@Override
			protected String call() throws Exception {
		        try {
		        	start = new Start();
		        	pageUrl = start.start(new StartupDetails.Builder(file).log(false).display(false).build());
		        	return pageUrl;
				} catch (Exception e1) {
					Logger.getLogger(this.getClass().getCanonicalName()).log(Level.SEVERE, "Failed to load server.", e1);
				}  
		        return null;
			}
		};
		startServer.setOnSucceeded(ev -> {
				String url = startServer.getValue();
				this.urlProperty.set(url);
				if (url!=null) {
					browser.getEngine().load(url);
					updateValidation();
				} else {
					browser.getEngine().load(getClass().getResource("resource-files/fail.html").toString());
				}
			}
		);
		Thread th = new Thread(startServer);
		th.setDaemon(true);
		th.start();
		if (pefWatcherThread!=null) {
			return f2 -> {
				pefWatcherThread.interrupt();
			};
		} else {
			return f2 -> {};
		}
	}
	
	/**
	 * Reloads the web view. This in turn, will trigger a file update, if the file
	 * has changed.
	 */
	public void reload() {
		browser.getEngine().reload();
		updateValidation();
	}
	
	private void updateValidation() {
		if (start!=null) {
			Optional res = start.getMainPage().getBookReaderResult();
			if (res.isPresent()) {
				validationReport.set(Optional.of(res.get().getValidationReport()));
			} else {
				validationReport.set(Optional.empty());
			}
		} else {
			validationReport.set(Optional.empty());
		}		
	}

	public ReadOnlyStringProperty urlProperty() {
		return urlProperty;
	}
	
	/**
	 * Informs the controller that it should close.
	 */
	public void closing() {
		closing = true;
		if (start!=null) {
			start.stopServer();
		}
	}
	
	private Optional getBookURI() {
		if (start!=null) {
			return Optional.of(start.getMainPage().getBookURI());
		} else {
			return Optional.empty();
		}
	}
	
	/**
	 * Shows the emboss dialog.
	 */
	public void showEmbossDialog() {
		if (start!=null) {
			Optional reader = start.getMainPage().getBookReaderResult();
			if (reader.isPresent() && reader.get().isValid()) {
				PEFBook book = reader.get().getBook();
				if (embossView==null) {
					embossView = new EmbossView(book);
					embossView.initOwner(this.getScene().getWindow());
					embossView.initModality(Modality.APPLICATION_MODAL); 
				} else {
					embossView.setBook(book);
				}
				embossView.showAndWait();
			} else {
				Alert alert = new Alert(AlertType.ERROR, Messages.ERROR_CANNOT_EMBOSS_INVALID_FILE.localize(), ButtonType.OK);
	    		alert.showAndWait();
			}
		}
	}

	@Override
	public void save() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean saveAs(File f) throws IOException {
		URI uri = getBookURI().orElseThrow(()->new IOException("Nothing to save."));		
		try {
			Files.copy(Paths.get(uri), new FileOutputStream(f));
			return true;
		} catch (IOException e) {
			throw e;
		}
	}

	@Override
	public List getSaveAsFilters() {
		return Arrays.asList(new ExtensionFilter(Messages.EXTENSION_FILTER_FILE.localize("PEF"), "*.pef"));
	}

	@Override
	public ObservableBooleanValue canEmboss() {
		return canEmbossProperty;
	}

	@Override
	public ObservableBooleanValue canSave() {
		return canSaveProperty;
	}
	
	@Override
	public ObservableBooleanValue canSaveAs() {
		return canSaveAsProperty;
	}

	@Override
	public void activate() {
		browser.requestFocus();
	}
	
	@Override
	public Node getNode() {
		return this;
	}

	@Override
	public void export(Window ownerWindow, ExportAction action) throws IOException {
		URI uri = getBookURI().orElseThrow(()->new IOException("Nothing to export."));
		File input = new File(uri);
		action.export(ownerWindow, input);
	}

	@Override
	public ObservableObjectValue fileDetails() {
		return fileDetails;
	}

	@Override
	public ObservableObjectValue> validationReport() {
		return validationReport;
	}
	
	@Override
	public boolean scrollTo(DocumentPosition location) {
		int volume = start.getMainPage().getVolumeForPosition(location);
		String url = pageUrl+"?book.xml&volume="+volume+"#"+StaxPreviewParser.messageId(location);
		browser.getEngine().load(url);
		// returns true if there is a validation message at the given location, false otherwise
		return validationReport.get()
				.map(v->v.getMessages().stream()).orElse(Stream.empty())
				.map(v->DocumentPosition.with(v.getLineNumber(), v.getColumnNumber()))
				.filter(v->v.equals(location))
				.count()>0;
	}

	@Override
	public boolean findNext(String text, SearchOptions opts) {
		return (Boolean)browser.getEngine().executeScript(
			String.format("self.find('%s', %b, %b, %b)", text, 
			opts.shouldMatchCase(), opts.shouldReverseSearch(), opts.shouldWrapAround())
		);
	}

	@Override
	public void replace(String replace) {
		// Not supported
	}

	@Override
	public ObservableObjectValue searchCapabilities() {
		return SEARCH_CAPABILITIES;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy