package com.scrivenvar;
import com.scrivenvar.definition.DefinitionFactory;
import com.scrivenvar.definition.DefinitionPane;
import com.scrivenvar.definition.DefinitionSource;
import com.scrivenvar.definition.MapInterpolator;
import com.scrivenvar.definition.yaml.YamlDefinitionSource;
import com.scrivenvar.dialogs.RScriptDialog;
import com.scrivenvar.editors.EditorPane;
import com.scrivenvar.editors.VariableNameInjector;
import com.scrivenvar.editors.markdown.MarkdownEditorPane;
import com.scrivenvar.preview.HTMLPreviewPane;
import com.scrivenvar.processors.Processor;
import com.scrivenvar.processors.ProcessorFactory;
import com.scrivenvar.service.Options;
import com.scrivenvar.service.Settings;
import com.scrivenvar.service.Snitch;
import com.scrivenvar.service.events.Notifier;
import com.scrivenvar.util.Action;
import com.scrivenvar.util.ActionUtils;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import org.controlsfx.control.StatusBar;
import org.fxmisc.richtext.model.TwoDimensional.Position;
import java.io.File;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.prefs.Preferences;
import static com.scrivenvar.Constants.*;
import static com.scrivenvar.Messages.get;
import static com.scrivenvar.Messages.getLiteral;
import static com.scrivenvar.util.StageState.*;
import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*;
import static javafx.event.Event.fireEvent;
import static javafx.scene.input.KeyCode.ENTER;
import static javafx.scene.input.KeyCode.TAB;
import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST;
public class MainWindow implements Observer {
private final Options mOptions = Services.load( Options.class );
private final Snitch mSnitch = Services.load( Snitch.class );
private final Settings mSettings = Services.load( Settings.class );
private final Notifier mNotifier = Services.load( Notifier.class );
private final Scene mScene;
private final StatusBar mStatusBar;
private final Text mLineNumberText;
private final TextField mFindTextField;
private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
private final DefinitionPane mDefinitionPane = new DefinitionPane();
private final HTMLPreviewPane mPreviewPane = new HTMLPreviewPane();
private FileEditorTabPane fileEditorPane;
private final Map<FileEditorTab, Processor<String>> mProcessors =
new HashMap<>();
private final Map<String, String> mResolvedMap =
new HashMap<>( DEFAULT_MAP_SIZE );
private VariableNameInjector variableNameInjector;
final EventHandler<TreeItem.TreeModificationEvent<Event>> mTreeHandler =
event -> {
exportDefinitions( getDefinitionPath() );
interpolateResolvedMap();
refreshActiveTab();
};
final EventHandler<? super KeyEvent> mDefinitionKeyHandler =
event -> {
if( event.getCode() == ENTER ) {
getVariableNameInjector().injectSelectedItem();
}
};
final EventHandler<? super KeyEvent> mEditorKeyHandler =
(EventHandler<KeyEvent>) event -> {
if( event.getCode() == TAB ) {
getDefinitionPane().requestFocus();
event.consume();
}
};
public MainWindow() {
mStatusBar = createStatusBar();
mLineNumberText = createLineNumberText();
mFindTextField = createFindTextField();
mScene = createScene();
initLayout();
initFindInput();
initSnitch();
initDefinitionListener();
initTabAddedListener();
initTabChangedListener();
initPreferences();
}
private void initSnitch() {
getSnitch().addObserver( this );
}
private void initFindInput() {
final TextField input = getFindTextField();
input.setOnKeyPressed( ( KeyEvent event ) -> {
switch( event.getCode() ) {
case F3:
case ENTER:
findNext();
break;
case F:
if( !event.isControlDown() ) {
break;
}
case ESCAPE:
getStatusBar().setGraphic( null );
getActiveFileEditor().getEditorPane().requestFocus();
break;
}
} );
input.focusedProperty().addListener(
(
final ObservableValue<? extends Boolean> focused,
final Boolean oFocus,
final Boolean nFocus ) -> {
if( !nFocus ) {
getStatusBar().setGraphic( null );
}
}
);
}
private void initDefinitionListener() {
getFileEditorPane().onOpenDefinitionFileProperty().addListener(
( final ObservableValue<? extends Path> file,
final Path oldPath, final Path newPath ) -> {
resetProcessors();
openDefinitions( newPath );
refreshActiveTab();
}
);
}
private void initTabAddedListener() {
final FileEditorTabPane editorPane = getFileEditorPane();
final ObservableList<Tab> tabs = editorPane.getTabs();
tabs.addListener(
( final Change<? extends Tab> change ) -> {
while( change.next() ) {
if( change.wasAdded() ) {
for( final Tab newTab : change.getAddedSubList() ) {
final FileEditorTab tab = (FileEditorTab) newTab;
initTextChangeListener( tab );
initCaretParagraphListener( tab );
initKeyboardEventListeners( tab );
}
}
}
}
);
}
private void initPreferences() {
restoreDefinitionPane();
getFileEditorPane().restorePreferences();
}
private void initTabChangedListener() {
final FileEditorTabPane editorPane = getFileEditorPane();
editorPane.addTabSelectionListener(
( ObservableValue<? extends Tab> tabPane,
final Tab oldTab, final Tab newTab ) -> {
updateVariableNameInjector();
if( oldTab != null ) {
if( newTab == null ) {
closeRemainingTab();
}
else {
refreshSelectedTab( (FileEditorTab) newTab );
}
}
}
);
}
private void initKeyboardEventListeners( final FileEditorTab tab ) {
final VariableNameInjector vin = getVariableNameInjector();
vin.initKeyboardEventListeners( tab );
tab.addEventFilter( KeyEvent.KEY_PRESSED, mEditorKeyHandler );
}
private void initTextChangeListener( final FileEditorTab tab ) {
tab.addTextChangeListener(
( ObservableValue<? extends String> editor,
final String oldValue, final String newValue ) ->
refreshSelectedTab( tab )
);
}
private void initCaretParagraphListener( final FileEditorTab tab ) {
tab.addCaretParagraphListener(
( ObservableValue<? extends Integer> editor,
final Integer oldValue, final Integer newValue ) ->
refreshSelectedTab( tab )
);
}
private void updateVariableNameInjector() {
getVariableNameInjector().setFileEditorTab( getActiveFileEditor() );
}
private void setVariableNameInjector( final VariableNameInjector injector ) {
this.variableNameInjector = injector;
}
private synchronized VariableNameInjector getVariableNameInjector() {
if( this.variableNameInjector == null ) {
final VariableNameInjector vin = createVariableNameInjector();
setVariableNameInjector( vin );
}
return this.variableNameInjector;
}
private VariableNameInjector createVariableNameInjector() {
final FileEditorTab tab = getActiveFileEditor();
final DefinitionPane pane = getDefinitionPane();
return new VariableNameInjector( tab, pane );
}
private void refreshSelectedTab( final FileEditorTab tab ) {
if( tab == null ) {
return;
}
getPreviewPane().setPath( tab.getPath() );
final Position p = tab.getCaretOffset();
getLineNumberText().setText(
get( STATUS_BAR_LINE,
p.getMajor() + 1,
p.getMinor() + 1,
tab.getCaretPosition() + 1
)
);
Processor<String> processor = getProcessors().get( tab );
if( processor == null ) {
processor = createProcessor( tab );
getProcessors().put( tab, processor );
}
try {
processor.processChain( tab.getEditorText() );
} catch( final Exception ex ) {
error( ex );
}
}
private void refreshActiveTab() {
refreshSelectedTab( getActiveFileEditor() );
}
private void find() {
final TextField input = getFindTextField();
getStatusBar().setGraphic( input );
input.requestFocus();
}
public void findNext() {
getActiveFileEditor().searchNext( getFindTextField().getText() );
}
private Map<String, String> getResolvedMap() {
return mResolvedMap;
}
private void interpolateResolvedMap() {
final Map<String, String> treeMap = getDefinitionPane().toMap();
final Map<String, String> map = new HashMap<>( treeMap );
MapInterpolator.interpolate( map );
getResolvedMap().clear();
getResolvedMap().putAll( map );
}
private void openDefinitions( final Path path ) {
try {
final DefinitionSource ds = createDefinitionSource( path );
setDefinitionSource( ds );
storeDefinitionSourceFilename( path );
final Tooltip tooltipPath = new Tooltip( path.toString() );
tooltipPath.setShowDelay( Duration.millis( 200 ) );
final DefinitionPane pane = getDefinitionPane();
pane.update( ds );
pane.addTreeChangeHandler( mTreeHandler );
pane.addKeyEventHandler( mDefinitionKeyHandler );
pane.filenameProperty().setValue( path.getFileName().toString() );
pane.setTooltip( tooltipPath );
interpolateResolvedMap();
} catch( final Exception e ) {
error( e );
}
}
private void exportDefinitions( final Path path ) {
try {
final DefinitionPane pane = getDefinitionPane();
final TreeItem<String> root = pane.getTreeView().getRoot();
final TreeItem<String> problemChild = pane.isTreeWellFormed();
if( problemChild == null ) {
getDefinitionSource().getTreeAdapter().export( root, path );
getNotifier().clear();
}
else {
final String msg = get( "yaml.error.tree.form",
problemChild.getValue() );
getNotifier().notify( msg );
}
} catch( final Exception e ) {
error( e );
}
}
private Path getDefinitionPath() {
final String source = getPreferences().get(
PERSIST_DEFINITION_SOURCE, "" );
return new File(
source.isBlank()
? getSetting( "file.definition.default", "variables.yaml" )
: source
).toPath();
}
private void restoreDefinitionPane() {
openDefinitions( getDefinitionPath() );
}
private void storeDefinitionSourceFilename( final Path path ) {
getPreferences().put( PERSIST_DEFINITION_SOURCE, path.toString() );
}
private void closeRemainingTab() {
getPreviewPane().clear();
}
private void error( final Exception e ) {
getNotifier().notify( e );
}
@Override
public void update( final Observable observable, final Object value ) {
if( value != null ) {
if( observable instanceof Snitch && value instanceof Path ) {
updateSelectedTab();
}
else if( observable instanceof Notifier && value instanceof String ) {
updateStatusBar( (String) value );
}
}
}
private void updateStatusBar( final String s ) {
Platform.runLater(
() -> {
final int index = s.indexOf( '\n' );
final String message = s.substring(
0, index > 0 ? index : s.length() );
getStatusBar().setText( message );
}
);
}
private void updateSelectedTab() {
Platform.runLater(
() -> {
resetProcessors();
refreshActiveTab();
}
);
}
private void resetProcessors() {
getProcessors().clear();
}
private void fileNew() {
getFileEditorPane().newEditor();
}
private void fileOpen() {
getFileEditorPane().openFileDialog();
}
private void fileClose() {
getFileEditorPane().closeEditor( getActiveFileEditor(), true );
}
private void fileCloseAll() {
getFileEditorPane().closeAllEditors();
}
private void fileSave() {
getFileEditorPane().saveEditor( getActiveFileEditor() );
}
private void fileSaveAs() {
final FileEditorTab editor = getActiveFileEditor();
getFileEditorPane().saveEditorAs( editor );
getProcessors().remove( editor );
try {
refreshSelectedTab( editor );
} catch( final Exception ex ) {
getNotifier().notify( ex );
}
}
private void fileSaveAll() {
getFileEditorPane().saveAllEditors();
}
private void fileExit() {
final Window window = getWindow();
fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
}
private void rScript() {
final String script = getPreferences().get( PERSIST_R_STARTUP, "" );
final RScriptDialog dialog = new RScriptDialog(
getWindow(), "Dialog.r.script.title", script );
final Optional<String> result = dialog.showAndWait();
result.ifPresent( this::putStartupScript );
}
private void rDirectory() {
final TextInputDialog dialog = new TextInputDialog(
getPreferences().get( PERSIST_R_DIRECTORY, USER_DIRECTORY )
);
dialog.setTitle( get( "Dialog.r.directory.title" ) );
dialog.setHeaderText( getLiteral( "Dialog.r.directory.header" ) );
dialog.setContentText( "Directory" );
final Optional<String> result = dialog.showAndWait();
result.ifPresent( this::putStartupDirectory );
}
private void putStartupScript( final String script ) {
putPreference( PERSIST_R_STARTUP, script );
}
private void putStartupDirectory( final String directory ) {
putPreference( PERSIST_R_DIRECTORY, directory );
}
private void helpAbout() {
final Alert alert = new Alert( AlertType.INFORMATION );
alert.setTitle( get( "Dialog.about.title" ) );
alert.setHeaderText( get( "Dialog.about.header" ) );
alert.setContentText( get( "Dialog.about.content" ) );
alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
alert.initOwner( getWindow() );
alert.showAndWait();
}
private float getFloat( final String key, final float defaultValue ) {
return getPreferences().getFloat( key, defaultValue );
}
private Preferences getPreferences() {
return getOptions().getState();
}
protected Scene getScene() {
return mScene;
}
public Window getWindow() {
return getScene().getWindow();
}
private MarkdownEditorPane getActiveEditor() {
final EditorPane pane = getActiveFileEditor().getEditorPane();
return pane instanceof MarkdownEditorPane
? (MarkdownEditorPane) pane
: null;
}
private FileEditorTab getActiveFileEditor() {
return getFileEditorPane().getActiveFileEditor();
}
private Map<FileEditorTab, Processor<String>> getProcessors() {
return mProcessors;
}
private FileEditorTabPane getFileEditorPane() {
if( this.fileEditorPane == null ) {
this.fileEditorPane = createFileEditorPane();
}
return this.fileEditorPane;
}
private HTMLPreviewPane getPreviewPane() {
return mPreviewPane;
}
private void setDefinitionSource( final DefinitionSource definitionSource ) {
assert definitionSource != null;
mDefinitionSource = definitionSource;
}
private DefinitionSource getDefinitionSource() {
return mDefinitionSource;
}
private DefinitionPane getDefinitionPane() {
return mDefinitionPane;
}
private Options getOptions() {
return mOptions;
}
private Snitch getSnitch() {
return mSnitch;
}
private Notifier getNotifier() {
return mNotifier;
}
private Text getLineNumberText() {
return mLineNumberText;
}
private StatusBar getStatusBar() {
return mStatusBar;
}
private TextField getFindTextField() {
return mFindTextField;
}
private Processor<String> createProcessor( final FileEditorTab tab ) {
return createProcessorFactory().createProcessor( tab );
}
private ProcessorFactory createProcessorFactory() {
return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
}
private DefinitionSource createDefaultDefinitionSource() {
return new YamlDefinitionSource( getDefinitionPath() );
}
private DefinitionSource createDefinitionSource( final Path path ) {
try {
return createDefinitionFactory().createDefinitionSource( path );
} catch( final Exception ex ) {
error( ex );
return createDefaultDefinitionSource();
}
}
private TextField createFindTextField() {
return new TextField();
}
private FileEditorTabPane createFileEditorPane() {
return new FileEditorTabPane();
}
private DefinitionFactory createDefinitionFactory() {
return new DefinitionFactory();
}
private StatusBar createStatusBar() {
return new StatusBar();
}
private Scene createScene() {
final SplitPane splitPane = new SplitPane(
getDefinitionPane().getNode(),
getFileEditorPane().getNode(),
getPreviewPane().getNode() );
splitPane.setDividerPositions(
getFloat( K_PANE_SPLIT_DEFINITION, .10f ),
getFloat( K_PANE_SPLIT_EDITOR, .45f ),
getFloat( K_PANE_SPLIT_PREVIEW, .45f ) );
getDefinitionPane().prefHeightProperty().bind( splitPane.heightProperty() );
final BorderPane borderPane = new BorderPane();
borderPane.setPrefSize( 1024, 800 );
borderPane.setTop( createMenuBar() );
borderPane.setBottom( getStatusBar() );
borderPane.setCenter( splitPane );
final VBox statusBar = new VBox();
statusBar.setAlignment( Pos.BASELINE_CENTER );
statusBar.getChildren().add( getLineNumberText() );
getStatusBar().getRightItems().add( statusBar );
return new Scene( borderPane );
}
private Text createLineNumberText() {
return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) );
}
private Node createMenuBar() {
final BooleanBinding activeFileEditorIsNull =
getFileEditorPane().activeFileEditorProperty()
.isNull();
final Action fileNewAction = new Action( get( "Main.menu.file.new" ),
"Shortcut+N", FILE_ALT,
e -> fileNew() );
final Action fileOpenAction = new Action( get( "Main.menu.file.open" ),
"Shortcut+O", FOLDER_OPEN_ALT,
e -> fileOpen() );
final Action fileCloseAction = new Action( get( "Main.menu.file.close" ),
"Shortcut+W", null,
e -> fileClose(),
activeFileEditorIsNull );
final Action fileCloseAllAction = new Action( get(
"Main.menu.file.close_all" ), null, null, e -> fileCloseAll(),
activeFileEditorIsNull );
final Action fileSaveAction = new Action( get( "Main.menu.file.save" ),
"Shortcut+S", FLOPPY_ALT,
e -> fileSave(),
createActiveBooleanProperty(
FileEditorTab::modifiedProperty )
.not() );
final Action fileSaveAsAction = new Action( Messages.get(
"Main.menu.file.save_as" ), null, null, e -> fileSaveAs(),
activeFileEditorIsNull );
final Action fileSaveAllAction = new Action(
get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null,
e -> fileSaveAll(),
Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
final Action fileExitAction = new Action( get( "Main.menu.file.exit" ),
null,
null,
e -> fileExit() );
final Action editUndoAction = new Action( get( "Main.menu.edit.undo" ),
"Shortcut+Z", UNDO,
e -> getActiveEditor().undo(),
createActiveBooleanProperty(
FileEditorTab::canUndoProperty )
.not() );
final Action editRedoAction = new Action( get( "Main.menu.edit.redo" ),
"Shortcut+Y", REPEAT,
e -> getActiveEditor().redo(),
createActiveBooleanProperty(
FileEditorTab::canRedoProperty )
.not() );
final Action editFindAction = new Action( Messages.get(
"Main.menu.edit.find" ), "Ctrl+F", SEARCH,
e -> find(),
activeFileEditorIsNull );
final Action editFindNextAction = new Action( Messages.get(
"Main.menu.edit.find.next" ), "F3", null,
e -> findNext(),
activeFileEditorIsNull );
final Action insertBoldAction = new Action( get( "Main.menu.insert.bold" ),
"Shortcut+B", BOLD,
e -> getActiveEditor().surroundSelection(
"**", "**" ),
activeFileEditorIsNull );
final Action insertItalicAction = new Action(
get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
e -> getActiveEditor().surroundSelection( "*", "*" ),
activeFileEditorIsNull );
final Action insertSuperscriptAction = new Action( get(
"Main.menu.insert.superscript" ), "Shortcut+[", SUPERSCRIPT,
e -> getActiveEditor().surroundSelection(
"^", "^" ),
activeFileEditorIsNull );
final Action insertSubscriptAction = new Action( get(
"Main.menu.insert.subscript" ), "Shortcut+]", SUBSCRIPT,
e -> getActiveEditor().surroundSelection(
"~", "~" ),
activeFileEditorIsNull );
final Action insertStrikethroughAction = new Action( get(
"Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
e -> getActiveEditor().surroundSelection(
"~~", "~~" ),
activeFileEditorIsNull );
final Action insertBlockquoteAction = new Action( get(
"Main.menu.insert.blockquote" ),
"Ctrl+Q",
QUOTE_LEFT,
e -> getActiveEditor().surroundSelection(
"\n\n> ", "" ),
activeFileEditorIsNull );
final Action insertCodeAction = new Action( get( "Main.menu.insert.code" ),
"Shortcut+K", CODE,
e -> getActiveEditor().surroundSelection(
"`", "`" ),
activeFileEditorIsNull );
final Action insertFencedCodeBlockAction = new Action( get(
"Main.menu.insert.fenced_code_block" ),
"Shortcut+Shift+K",
FILE_CODE_ALT,
e -> getActiveEditor()
.surroundSelection(
"\n\n```\n",
"\n```\n\n",
get(
"Main.menu.insert.fenced_code_block.prompt" ) ),
activeFileEditorIsNull );
final Action insertLinkAction = new Action( get( "Main.menu.insert.link" ),
"Shortcut+L", LINK,
e -> getActiveEditor().insertLink(),
activeFileEditorIsNull );
final Action insertImageAction = new Action( get( "Main.menu.insert" +
".image" ),
"Shortcut+G", PICTURE_ALT,
e -> getActiveEditor().insertImage(),
activeFileEditorIsNull );
final int HEADERS = 3;
final Action[] headers = new Action[ HEADERS ];
for( int i = 1; i <= HEADERS; i++ ) {
final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
final String markup = String.format( "%n%n%s ", hashes );
final String text = get( "Main.menu.insert.header_" + i );
final String accelerator = "Shortcut+" + i;
final String prompt = get( "Main.menu.insert.header_" + i + ".prompt" );
headers[ i - 1 ] = new Action( text, accelerator, HEADER,
e -> getActiveEditor().surroundSelection(
markup, "", prompt ),
activeFileEditorIsNull );
}
final Action insertUnorderedListAction = new Action(
get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
activeFileEditorIsNull );
final Action insertOrderedListAction = new Action(
get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
activeFileEditorIsNull );
final Action insertHorizontalRuleAction = new Action(
get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
activeFileEditorIsNull );
final Action mRScriptAction = new Action(
get( "Main.menu.r.script" ), null, null, e -> rScript() );
final Action mRDirectoryAction = new Action(
get( "Main.menu.r.directory" ), null, null, e -> rDirectory() );
final Action helpAboutAction = new Action(
get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
final Menu fileMenu = ActionUtils.createMenu(
get( "Main.menu.file" ),
fileNewAction,
fileOpenAction,
null,
fileCloseAction,
fileCloseAllAction,
null,
fileSaveAction,
fileSaveAsAction,
fileSaveAllAction,
null,
fileExitAction );
final Menu editMenu = ActionUtils.createMenu(
get( "Main.menu.edit" ),
editUndoAction,
editRedoAction,
editFindAction,
editFindNextAction );
final Menu insertMenu = ActionUtils.createMenu(
get( "Main.menu.insert" ),
insertBoldAction,
insertItalicAction,
insertSuperscriptAction,
insertSubscriptAction,
insertStrikethroughAction,
insertBlockquoteAction,
insertCodeAction,
insertFencedCodeBlockAction,
null,
insertLinkAction,
insertImageAction,
null,
headers[ 0 ],
headers[ 1 ],
headers[ 2 ],
null,
insertUnorderedListAction,
insertOrderedListAction,
insertHorizontalRuleAction );
final Menu rMenu = ActionUtils.createMenu(
get( "Main.menu.r" ),
mRScriptAction,
mRDirectoryAction );
final Menu helpMenu = ActionUtils.createMenu(
get( "Main.menu.help" ),
helpAboutAction );
final MenuBar menuBar = new MenuBar(
fileMenu,
editMenu,
insertMenu,
rMenu,
helpMenu );
final ToolBar toolBar = ActionUtils.createToolBar(
fileNewAction,
fileOpenAction,
fileSaveAction,
null,
editUndoAction,
editRedoAction,
null,
insertBoldAction,
insertItalicAction,
insertSuperscriptAction,
insertSubscriptAction,
insertBlockquoteAction,
insertCodeAction,
insertFencedCodeBlockAction,
null,
insertLinkAction,
insertImageAction,
null,
headers[ 0 ],
null,
insertUnorderedListAction,
insertOrderedListAction );
return new VBox( menuBar, toolBar );
}
private BooleanProperty createActiveBooleanProperty(
final Function<FileEditorTab, ObservableBooleanValue> func ) {
final BooleanProperty b = new SimpleBooleanProperty();
final FileEditorTab tab = getActiveFileEditor();
if( tab != null ) {
b.bind( func.apply( tab ) );
}
getFileEditorPane().activeFileEditorProperty().addListener(
( observable, oldFileEditor, newFileEditor ) -> {
b.unbind();
if( newFileEditor != null ) {
b.bind( func.apply( newFileEditor ) );
}
else {
b.set( false );
}
}
);
return b;
}
private void initLayout() {
final Scene appScene = getScene();
appScene.getStylesheets().add( STYLESHEET_SCENE );
appScene.windowProperty().addListener(
( observable, oldWindow, newWindow ) ->
newWindow.setOnCloseRequest(
e -> {
if( !getFileEditorPane().closeAllEditors() ) {
e.consume();
}
}
)
);
}
private void putPreference( final String key, final String value ) {
try {
getPreferences().put( key, value );
} catch( final Exception ex ) {
getNotifier().notify( ex );
}
}
@SuppressWarnings("SameParameterValue")
private String getSetting( final String key, final String value ) {
return mSettings.getSetting( key, value );
}
}