package com.scrivenvar;
import com.scrivenvar.editor.EditorPane;
import com.scrivenvar.editor.MarkdownEditorPane;
import com.scrivenvar.preview.HTMLPreviewPane;
import com.scrivenvar.service.Options;
import com.scrivenvar.service.events.AlertMessage;
import com.scrivenvar.service.events.AlertService;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.Event;
import javafx.scene.control.Alert;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.Tooltip;
import javafx.scene.input.InputEvent;
import javafx.scene.text.Text;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.StyleClassedTextArea;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.wellbehaved.event.EventPattern;
import org.fxmisc.wellbehaved.event.InputMap;
public final class FileEditorTab extends Tab {
private final Options options = Services.load( Options.class );
private final AlertService alertService = Services.load( AlertService.class );
private EditorPane editorPane;
private HTMLPreviewPane previewPane;
private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper();
private final BooleanProperty canUndo = new SimpleBooleanProperty();
private final BooleanProperty canRedo = new SimpleBooleanProperty();
private Path path;
FileEditorTab( final Path path ) {
setPath( path );
setUserData( this );
this.modified.addListener( (observable, oldPath, newPath) -> updateTab() );
updateTab();
setOnSelectionChanged( e -> {
if( isSelected() ) {
Platform.runLater( () -> activated() );
}
} );
}
private void updateTab() {
final Path filePath = getPath();
setText( getFilename( filePath ) );
setGraphic( getModifiedMark() );
setTooltip( getTooltip( filePath ) );
}
private String getFilename( final Path filePath ) {
return (filePath == null)
? Messages.get( "FileEditor.untitled" )
: filePath.getFileName().toString();
}
private Tooltip getTooltip( final Path filePath ) {
return (filePath == null)
? null
: new Tooltip( filePath.toString() );
}
private Text getModifiedMark() {
return isModified() ? new Text( "*" ) : null;
}
private void activated() {
if( getTabPane() == null || !isSelected() ) {
return;
}
if( getContent() != null ) {
getEditorPane().requestFocus();
return;
}
load();
initUndoManager();
initSplitPane();
}
public void initSplitPane() {
final EditorPane editor = getEditorPane();
final HTMLPreviewPane preview = getPreviewPane();
final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane = editor.getScrollPane();
final SplitPane splitPane = new SplitPane(
editorScrollPane,
preview.getWebView() );
setContent( splitPane );
editor.requestFocus();
}
private void initUndoManager() {
final UndoManager undoManager = getUndoManager();
undoManager.forgetHistory();
modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) );
canUndo.bind( undoManager.undoAvailableProperty() );
canRedo.bind( undoManager.redoAvailableProperty() );
}
public int getCaretPosition() {
return getEditorPane().getEditor().getCaretPosition();
}
void load() {
final Path filePath = getPath();
if( filePath != null ) {
try {
final byte[] bytes = Files.readAllBytes( filePath );
String markdown;
try {
markdown = new String( bytes, getOptions().getEncoding() );
} catch( Exception e ) {
markdown = new String( bytes );
}
getEditorPane().setText( markdown );
} catch( IOException ex ) {
final AlertMessage message = getAlertService().createAlertMessage(
Messages.get( "FileEditor.loadFailed.title" ),
Messages.get( "FileEditor.loadFailed.message" ),
filePath,
ex.getMessage()
);
final Alert alert = getAlertService().createAlertError( message );
alert.showAndWait();
}
}
}
boolean save() {
final String text = getEditorPane().getText();
byte[] bytes;
try {
bytes = text.getBytes( getOptions().getEncoding() );
} catch( Exception ex ) {
bytes = text.getBytes();
}
try {
Files.write( getPath(), bytes );
getEditorPane().getUndoManager().mark();
return true;
} catch( IOException ex ) {
final AlertService service = getAlertService();
final AlertMessage message = service.createAlertMessage(
Messages.get( "FileEditor.saveFailed.title" ),
Messages.get( "FileEditor.saveFailed.message" ),
getPath(),
ex.getMessage()
);
final Alert alert = service.createAlertError( message );
alert.showAndWait();
return false;
}
}
Path getPath() {
return this.path;
}
void setPath( final Path path ) {
this.path = path;
}
boolean isModified() {
return this.modified.get();
}
ReadOnlyBooleanProperty modifiedProperty() {
return this.modified.getReadOnlyProperty();
}
BooleanProperty canUndoProperty() {
return this.canUndo;
}
BooleanProperty canRedoProperty() {
return this.canRedo;
}
private UndoManager getUndoManager() {
return getEditorPane().getUndoManager();
}
public <T extends Event, U extends T> void addEventListener(
final EventPattern<? super T, ? extends U> event,
final Consumer<? super U> consumer ) {
getEditorPane().addEventListener( event, consumer );
}
public void addEventListener( final InputMap<InputEvent> map ) {
getEditorPane().addEventListener( map );
}
public void removeEventListener( final InputMap<InputEvent> map ) {
getEditorPane().removeEventListener( map );
}
protected EditorPane getEditorPane() {
if( this.editorPane == null ) {
this.editorPane = new MarkdownEditorPane();
}
return this.editorPane;
}
private AlertService getAlertService() {
return this.alertService;
}
private Options getOptions() {
return this.options;
}
public HTMLPreviewPane getPreviewPane() {
if( this.previewPane == null ) {
this.previewPane = new HTMLPreviewPane( getPath() );
}
return this.previewPane;
}
}