| Author | djarvis <email> |
|---|---|
| Date | 2016-10-16 22:48:50 GMT-0700 |
| Commit | 2e76df97fcb14d51c0704d770c643fb50af08680 |
| Parent | 5969a1e |
| Delta | 936 lines added, 690 lines removed, 246-line increase |
| public static String[] getPrefsStrings(Preferences prefs, String key) { | ||
| - ArrayList<String> arr = new ArrayList<String>(); | ||
| + ArrayList<String> arr = new ArrayList<>(); | ||
| for (int i = 0; i < 10000; i++) { | ||
| String s = prefs.get(key + (i + 1), null); |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
| - | ||
| -package org.markdownwriterfx; | ||
| - | ||
| -import java.io.File; | ||
| -import java.nio.file.Path; | ||
| -import java.util.ArrayList; | ||
| -import java.util.List; | ||
| -import java.util.prefs.Preferences; | ||
| -import javafx.beans.property.ReadOnlyBooleanProperty; | ||
| -import javafx.beans.property.ReadOnlyBooleanWrapper; | ||
| -import javafx.beans.property.ReadOnlyObjectProperty; | ||
| -import javafx.beans.property.ReadOnlyObjectWrapper; | ||
| -import javafx.beans.value.ChangeListener; | ||
| -import javafx.collections.ListChangeListener; | ||
| -import javafx.collections.ObservableList; | ||
| -import javafx.event.Event; | ||
| -import javafx.scene.Node; | ||
| -import javafx.scene.control.Alert; | ||
| -import javafx.scene.control.Alert.AlertType; | ||
| -import javafx.scene.control.ButtonType; | ||
| -import javafx.scene.control.Tab; | ||
| -import javafx.scene.control.TabPane; | ||
| -import javafx.scene.control.TabPane.TabClosingPolicy; | ||
| -import javafx.stage.FileChooser; | ||
| -import javafx.stage.FileChooser.ExtensionFilter; | ||
| -import org.markdownwriterfx.util.Utils; | ||
| - | ||
| -/** | ||
| - * Tab pane for file editors. | ||
| - * | ||
| - * @author Karl Tauber | ||
| - */ | ||
| -class FileEditorTabPane | ||
| -{ | ||
| - private final MainWindow mainWindow; | ||
| - private final TabPane tabPane; | ||
| - private final ReadOnlyObjectWrapper<FileEditor> activeFileEditor = new ReadOnlyObjectWrapper<>(); | ||
| - private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper(); | ||
| - | ||
| - FileEditorTabPane(MainWindow mainWindow) { | ||
| - this.mainWindow = mainWindow; | ||
| - | ||
| - tabPane = new TabPane(); | ||
| - tabPane.setFocusTraversable(false); | ||
| - tabPane.setTabClosingPolicy(TabClosingPolicy.ALL_TABS); | ||
| - | ||
| - // update activeFileEditor property | ||
| - tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldTab, newTab) -> { | ||
| - activeFileEditor.set((newTab != null) ? (FileEditor) newTab.getUserData() : null); | ||
| - }); | ||
| - | ||
| - // update anyFileEditorModified property | ||
| - ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> { | ||
| - boolean modified = false; | ||
| - for (Tab tab : tabPane.getTabs()) { | ||
| - if (((FileEditor)tab.getUserData()).isModified()) { | ||
| - modified = true; | ||
| - break; | ||
| - } | ||
| - } | ||
| - anyFileEditorModified.set(modified); | ||
| - }; | ||
| - tabPane.getTabs().addListener((ListChangeListener<Tab>) c -> { | ||
| - while (c.next()) { | ||
| - if (c.wasAdded()) { | ||
| - for (Tab tab : c.getAddedSubList()) | ||
| - ((FileEditor)tab.getUserData()).modifiedProperty().addListener(modifiedListener); | ||
| - } else if (c.wasRemoved()) { | ||
| - for (Tab tab : c.getRemoved()) | ||
| - ((FileEditor)tab.getUserData()).modifiedProperty().removeListener(modifiedListener); | ||
| - } | ||
| - } | ||
| - | ||
| - // changes in the tabs may also change anyFileEditorModified property | ||
| - // (e.g. closed modified file) | ||
| - modifiedListener.changed(null, null, null); | ||
| - }); | ||
| - | ||
| - // re-open files | ||
| - restoreState(); | ||
| - } | ||
| - | ||
| - Node getNode() { | ||
| - return tabPane; | ||
| - } | ||
| - | ||
| - // 'activeFileEditor' property | ||
| - FileEditor getActiveFileEditor() { return activeFileEditor.get(); } | ||
| - ReadOnlyObjectProperty<FileEditor> activeFileEditorProperty() { | ||
| - return activeFileEditor.getReadOnlyProperty(); | ||
| - } | ||
| - | ||
| - // 'anyFileEditorModified' property | ||
| - ReadOnlyBooleanProperty anyFileEditorModifiedProperty() { | ||
| - return anyFileEditorModified.getReadOnlyProperty(); | ||
| - } | ||
| - | ||
| - private FileEditor createFileEditor(Path path) { | ||
| - FileEditor fileEditor = new FileEditor(mainWindow, path); | ||
| - fileEditor.getTab().setOnCloseRequest(e -> { | ||
| - if (!canCloseEditor(fileEditor)) | ||
| - e.consume(); | ||
| - }); | ||
| - return fileEditor; | ||
| - } | ||
| - | ||
| - FileEditor newEditor() { | ||
| - FileEditor fileEditor = createFileEditor(null); | ||
| - Tab tab = fileEditor.getTab(); | ||
| - tabPane.getTabs().add(tab); | ||
| - tabPane.getSelectionModel().select(tab); | ||
| - return fileEditor; | ||
| - } | ||
| - | ||
| - FileEditor[] openEditor() { | ||
| - FileChooser fileChooser = createFileChooser(Messages.get("FileEditorTabPane.openChooser.title")); | ||
| - List<File> selectedFiles = fileChooser.showOpenMultipleDialog(mainWindow.getScene().getWindow()); | ||
| - if (selectedFiles == null) | ||
| - return null; | ||
| - | ||
| - saveLastDirectory(selectedFiles.get(0)); | ||
| - return openEditors(selectedFiles, 0); | ||
| - } | ||
| - | ||
| - FileEditor[] openEditors(List<File> files, int activeIndex) { | ||
| - // close single unmodified "Untitled" tab | ||
| - if (tabPane.getTabs().size() == 1) { | ||
| - FileEditor fileEditor = (FileEditor) tabPane.getTabs().get(0).getUserData(); | ||
| - if (fileEditor.getPath() == null && !fileEditor.isModified()) | ||
| - closeEditor(fileEditor, false); | ||
| - } | ||
| - | ||
| - FileEditor[] fileEditors = new FileEditor[files.size()]; | ||
| - for (int i = 0; i < files.size(); i++) { | ||
| - Path path = files.get(i).toPath(); | ||
| - | ||
| - // check whether file is already opened | ||
| - FileEditor fileEditor = findEditor(path); | ||
| - if (fileEditor == null) { | ||
| - fileEditor = createFileEditor(path); | ||
| - | ||
| - tabPane.getTabs().add(fileEditor.getTab()); | ||
| - } | ||
| - | ||
| - // select first file | ||
| - if (i == activeIndex) | ||
| - tabPane.getSelectionModel().select(fileEditor.getTab()); | ||
| - | ||
| - fileEditors[i] = fileEditor; | ||
| - } | ||
| - return fileEditors; | ||
| - } | ||
| - | ||
| - boolean saveEditor(FileEditor fileEditor) { | ||
| - if (fileEditor == null || !fileEditor.isModified()) | ||
| - return true; | ||
| - | ||
| - if (fileEditor.getPath() == null) { | ||
| - tabPane.getSelectionModel().select(fileEditor.getTab()); | ||
| - | ||
| - FileChooser fileChooser = createFileChooser(Messages.get("FileEditorTabPane.saveChooser.title")); | ||
| - File file = fileChooser.showSaveDialog(mainWindow.getScene().getWindow()); | ||
| - if (file == null) | ||
| - return false; | ||
| - | ||
| - saveLastDirectory(file); | ||
| - fileEditor.setPath(file.toPath()); | ||
| - } | ||
| - | ||
| - return fileEditor.save(); | ||
| - } | ||
| - | ||
| - boolean saveAllEditors() { | ||
| - FileEditor[] allEditors = getAllEditors(); | ||
| - | ||
| - boolean success = true; | ||
| - for (FileEditor fileEditor : allEditors) { | ||
| - if (!saveEditor(fileEditor)) | ||
| - success = false; | ||
| - } | ||
| - | ||
| - return success; | ||
| - } | ||
| - | ||
| - boolean canCloseEditor(FileEditor fileEditor) { | ||
| - if (!fileEditor.isModified()) | ||
| - return true; | ||
| - | ||
| - Alert alert = mainWindow.createAlert(AlertType.CONFIRMATION, | ||
| - Messages.get("FileEditorTabPane.closeAlert.title"), | ||
| - Messages.get("FileEditorTabPane.closeAlert.message"), fileEditor.getTab().getText()); | ||
| - alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL); | ||
| - | ||
| - ButtonType result = alert.showAndWait().get(); | ||
| - if (result != ButtonType.YES) | ||
| - return (result == ButtonType.NO); | ||
| - | ||
| - return saveEditor(fileEditor); | ||
| - } | ||
| - | ||
| - boolean closeEditor(FileEditor fileEditor, boolean save) { | ||
| - if (fileEditor == null) | ||
| - return true; | ||
| - | ||
| - Tab tab = fileEditor.getTab(); | ||
| - | ||
| - if (save) { | ||
| - Event event = new Event(tab,tab,Tab.TAB_CLOSE_REQUEST_EVENT); | ||
| - Event.fireEvent(tab, event); | ||
| - if (event.isConsumed()) | ||
| - return false; | ||
| - } | ||
| - | ||
| - tabPane.getTabs().remove(tab); | ||
| - if (tab.getOnClosed() != null) | ||
| - Event.fireEvent(tab, new Event(Tab.CLOSED_EVENT)); | ||
| - | ||
| - return true; | ||
| - } | ||
| - | ||
| - boolean closeAllEditors() { | ||
| - FileEditor[] allEditors = getAllEditors(); | ||
| - FileEditor activeEditor = activeFileEditor.get(); | ||
| - | ||
| - // try to save active tab first because in case the user decides to cancel, | ||
| - // then it stays active | ||
| - if (activeEditor != null && !canCloseEditor(activeEditor)) | ||
| - return false; | ||
| - | ||
| - // save modified tabs | ||
| - for (int i = 0; i < allEditors.length; i++) { | ||
| - FileEditor fileEditor = allEditors[i]; | ||
| - if (fileEditor == activeEditor) | ||
| - continue; | ||
| - | ||
| - if (fileEditor.isModified()) { | ||
| - // activate the modified tab to make its modified content visible to the user | ||
| - tabPane.getSelectionModel().select(i); | ||
| - | ||
| - if (!canCloseEditor(fileEditor)) | ||
| - return false; | ||
| - } | ||
| - } | ||
| - | ||
| - // close all tabs | ||
| - for (FileEditor fileEditor : allEditors) { | ||
| - if (!closeEditor(fileEditor, false)) | ||
| - return false; | ||
| - } | ||
| - | ||
| - saveState(allEditors, activeEditor); | ||
| - | ||
| - return tabPane.getTabs().isEmpty(); | ||
| - } | ||
| - | ||
| - private FileEditor[] getAllEditors() { | ||
| - ObservableList<Tab> tabs = tabPane.getTabs(); | ||
| - FileEditor[] allEditors = new FileEditor[tabs.size()]; | ||
| - for (int i = 0; i < tabs.size(); i++) | ||
| - allEditors[i] = (FileEditor) tabs.get(i).getUserData(); | ||
| - return allEditors; | ||
| - } | ||
| - | ||
| - private FileEditor findEditor(Path path) { | ||
| - for (Tab tab : tabPane.getTabs()) { | ||
| - FileEditor fileEditor = (FileEditor) tab.getUserData(); | ||
| - if (path.equals(fileEditor.getPath())) | ||
| - return fileEditor; | ||
| - } | ||
| - return null; | ||
| - } | ||
| - | ||
| - private FileChooser createFileChooser(String title) { | ||
| - FileChooser fileChooser = new FileChooser(); | ||
| - fileChooser.setTitle(title); | ||
| - fileChooser.getExtensionFilters().addAll( | ||
| - new ExtensionFilter(Messages.get("FileEditorTabPane.chooser.markdownFilesFilter"), "*.md", "*.markdown", "*.txt"), | ||
| - new ExtensionFilter(Messages.get("FileEditorTabPane.chooser.allFilesFilter"), "*.*")); | ||
| - | ||
| - String lastDirectory = MarkdownWriterFXApp.getState().get("lastDirectory", null); | ||
| - File file = new File((lastDirectory != null) ? lastDirectory : "."); | ||
| - if (!file.isDirectory()) | ||
| - file = new File("."); | ||
| - fileChooser.setInitialDirectory(file); | ||
| - return fileChooser; | ||
| - } | ||
| - | ||
| - private void saveLastDirectory(File file) { | ||
| - MarkdownWriterFXApp.getState().put("lastDirectory", file.getParent()); | ||
| - } | ||
| - | ||
| - private void restoreState() { | ||
| - Preferences state = MarkdownWriterFXApp.getState(); | ||
| - String[] fileNames = Utils.getPrefsStrings(state, "file"); | ||
| - String activeFileName = state.get("activeFile", null); | ||
| - | ||
| - int activeIndex = 0; | ||
| - ArrayList<File> files = new ArrayList<>(fileNames.length); | ||
| - for (String fileName : fileNames) { | ||
| - File file = new File(fileName); | ||
| - if (file.exists()) { | ||
| - files.add(file); | ||
| - | ||
| - if (fileName.equals(activeFileName)) | ||
| - activeIndex = files.size() - 1; | ||
| - } | ||
| - } | ||
| - | ||
| - if (files.isEmpty()) { | ||
| - newEditor(); | ||
| - return; | ||
| - } | ||
| - | ||
| - openEditors(files, activeIndex); | ||
| - } | ||
| - | ||
| - private void saveState(FileEditor[] allEditors, FileEditor activeEditor) { | ||
| - ArrayList<String> fileNames = new ArrayList<>(allEditors.length); | ||
| - for (FileEditor fileEditor : allEditors) { | ||
| - if (fileEditor.getPath() != null) | ||
| - fileNames.add(fileEditor.getPath().toString()); | ||
| - } | ||
| - | ||
| - Preferences state = MarkdownWriterFXApp.getState(); | ||
| - Utils.putPrefsStrings(state, "file", fileNames.toArray(new String[fileNames.size()])); | ||
| - if (activeEditor != null && activeEditor.getPath() != null) | ||
| - state.put("activeFile", activeEditor.getPath().toString()); | ||
| - else | ||
| - state.remove("activeFile"); | ||
| - } | ||
| +package org.markdownwriterfx; | ||
| + | ||
| +import java.io.File; | ||
| +import java.nio.file.Path; | ||
| +import java.util.ArrayList; | ||
| +import java.util.List; | ||
| +import java.util.Objects; | ||
| +import java.util.prefs.Preferences; | ||
| +import java.util.stream.Collectors; | ||
| +import javafx.beans.property.ReadOnlyBooleanProperty; | ||
| +import javafx.beans.property.ReadOnlyBooleanWrapper; | ||
| +import javafx.beans.property.ReadOnlyObjectProperty; | ||
| +import javafx.beans.property.ReadOnlyObjectWrapper; | ||
| +import javafx.beans.value.ChangeListener; | ||
| +import javafx.collections.ListChangeListener; | ||
| +import javafx.collections.ObservableList; | ||
| +import javafx.event.Event; | ||
| +import javafx.scene.Node; | ||
| +import javafx.scene.control.Alert; | ||
| +import javafx.scene.control.Alert.AlertType; | ||
| +import javafx.scene.control.ButtonType; | ||
| +import javafx.scene.control.Tab; | ||
| +import javafx.scene.control.TabPane; | ||
| +import javafx.scene.control.TabPane.TabClosingPolicy; | ||
| +import javafx.stage.FileChooser; | ||
| +import javafx.stage.FileChooser.ExtensionFilter; | ||
| +import static org.markdownwriterfx.options.DefaultConfiguration.DEFAULT_EXTENSIONS_ALL; | ||
| +import static org.markdownwriterfx.options.DefaultConfiguration.DEFAULT_EXTENSIONS_MARKDOWN; | ||
| +import org.markdownwriterfx.util.Utils; | ||
| + | ||
| +/** | ||
| + * Tab pane for file editors. | ||
| + * | ||
| + * @author Karl Tauber | ||
| + */ | ||
| +class FileEditorTabPane implements ApplicationProperty { | ||
| + | ||
| + private MainWindow mainWindow; | ||
| + private final TabPane tabPane; | ||
| + private final ReadOnlyObjectWrapper<FileEditor> activeFileEditor = new ReadOnlyObjectWrapper<>(); | ||
| + private final ReadOnlyBooleanWrapper anyFileEditorModified = new ReadOnlyBooleanWrapper(); | ||
| + | ||
| + FileEditorTabPane( MainWindow mainWindow ) { | ||
| + setMainWindow( mainWindow ); | ||
| + | ||
| + tabPane = new TabPane(); | ||
| + tabPane.setFocusTraversable( false ); | ||
| + tabPane.setTabClosingPolicy( TabClosingPolicy.ALL_TABS ); | ||
| + | ||
| + // update activeFileEditor property | ||
| + tabPane.getSelectionModel().selectedItemProperty().addListener( (observable, oldTab, newTab) -> { | ||
| + activeFileEditor.set( (newTab != null) ? (FileEditor)newTab.getUserData() : null ); | ||
| + } ); | ||
| + | ||
| + // update anyFileEditorModified property | ||
| + ChangeListener<Boolean> modifiedListener = (observable, oldValue, newValue) -> { | ||
| + boolean modified = false; | ||
| + for( Tab tab : tabPane.getTabs() ) { | ||
| + if( ((FileEditor)tab.getUserData()).isModified() ) { | ||
| + modified = true; | ||
| + break; | ||
| + } | ||
| + } | ||
| + anyFileEditorModified.set( modified ); | ||
| + }; | ||
| + | ||
| + tabPane.getTabs().addListener( (ListChangeListener<Tab>)c -> { | ||
| + while( c.next() ) { | ||
| + if( c.wasAdded() ) { | ||
| + for( Tab tab : c.getAddedSubList() ) { | ||
| + ((FileEditor)tab.getUserData()).modifiedProperty().addListener( modifiedListener ); | ||
| + } | ||
| + } else if( c.wasRemoved() ) { | ||
| + for( Tab tab : c.getRemoved() ) { | ||
| + ((FileEditor)tab.getUserData()).modifiedProperty().removeListener( modifiedListener ); | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + // changes in the tabs may also change anyFileEditorModified property | ||
| + // (e.g. closed modified file) | ||
| + modifiedListener.changed( null, null, null ); | ||
| + } ); | ||
| + | ||
| + // re-open files | ||
| + restoreState(); | ||
| + } | ||
| + | ||
| + private MainWindow getMainWindow() { | ||
| + return this.mainWindow; | ||
| + } | ||
| + | ||
| + private void setMainWindow( MainWindow mainWindow ) { | ||
| + this.mainWindow = mainWindow; | ||
| + } | ||
| + | ||
| + Node getNode() { | ||
| + return tabPane; | ||
| + } | ||
| + | ||
| + // 'activeFileEditor' property | ||
| + FileEditor getActiveFileEditor() { | ||
| + return activeFileEditor.get(); | ||
| + } | ||
| + | ||
| + ReadOnlyObjectProperty<FileEditor> activeFileEditorProperty() { | ||
| + return activeFileEditor.getReadOnlyProperty(); | ||
| + } | ||
| + | ||
| + // 'anyFileEditorModified' property | ||
| + ReadOnlyBooleanProperty anyFileEditorModifiedProperty() { | ||
| + return anyFileEditorModified.getReadOnlyProperty(); | ||
| + } | ||
| + | ||
| + private FileEditor createFileEditor( Path path ) { | ||
| + FileEditor fileEditor = new FileEditor( getMainWindow(), path ); | ||
| + fileEditor.getTab().setOnCloseRequest( e -> { | ||
| + if( !canCloseEditor( fileEditor ) ) { | ||
| + e.consume(); | ||
| + } | ||
| + } ); | ||
| + return fileEditor; | ||
| + } | ||
| + | ||
| + FileEditor newEditor() { | ||
| + FileEditor fileEditor = createFileEditor( null ); | ||
| + Tab tab = fileEditor.getTab(); | ||
| + tabPane.getTabs().add( tab ); | ||
| + tabPane.getSelectionModel().select( tab ); | ||
| + return fileEditor; | ||
| + } | ||
| + | ||
| + FileEditor[] openEditor() { | ||
| + FileChooser fileChooser = createFileChooser( Messages.get( "FileEditorTabPane.openChooser.title" ) ); | ||
| + List<File> selectedFiles = fileChooser.showOpenMultipleDialog( getMainWindow().getScene().getWindow() ); | ||
| + if( selectedFiles == null ) { | ||
| + return null; | ||
| + } | ||
| + | ||
| + saveLastDirectory( selectedFiles.get( 0 ) ); | ||
| + return openEditors( selectedFiles, 0 ); | ||
| + } | ||
| + | ||
| + FileEditor[] openEditors( List<File> files, int activeIndex ) { | ||
| + // close single unmodified "Untitled" tab | ||
| + if( tabPane.getTabs().size() == 1 ) { | ||
| + FileEditor fileEditor = (FileEditor)tabPane.getTabs().get( 0 ).getUserData(); | ||
| + if( fileEditor.getPath() == null && !fileEditor.isModified() ) { | ||
| + closeEditor( fileEditor, false ); | ||
| + } | ||
| + } | ||
| + | ||
| + FileEditor[] fileEditors = new FileEditor[ files.size() ]; | ||
| + for( int i = 0; i < files.size(); i++ ) { | ||
| + Path path = files.get( i ).toPath(); | ||
| + | ||
| + // check whether file is already opened | ||
| + FileEditor fileEditor = findEditor( path ); | ||
| + if( fileEditor == null ) { | ||
| + fileEditor = createFileEditor( path ); | ||
| + | ||
| + tabPane.getTabs().add( fileEditor.getTab() ); | ||
| + } | ||
| + | ||
| + // select first file | ||
| + if( i == activeIndex ) { | ||
| + tabPane.getSelectionModel().select( fileEditor.getTab() ); | ||
| + } | ||
| + | ||
| + fileEditors[ i ] = fileEditor; | ||
| + } | ||
| + return fileEditors; | ||
| + } | ||
| + | ||
| + boolean saveEditor( FileEditor fileEditor ) { | ||
| + if( fileEditor == null || !fileEditor.isModified() ) { | ||
| + return true; | ||
| + } | ||
| + | ||
| + if( fileEditor.getPath() == null ) { | ||
| + tabPane.getSelectionModel().select( fileEditor.getTab() ); | ||
| + | ||
| + FileChooser fileChooser = createFileChooser( Messages.get( "FileEditorTabPane.saveChooser.title" ) ); | ||
| + File file = fileChooser.showSaveDialog( getMainWindow().getScene().getWindow() ); | ||
| + if( file == null ) { | ||
| + return false; | ||
| + } | ||
| + | ||
| + saveLastDirectory( file ); | ||
| + fileEditor.setPath( file.toPath() ); | ||
| + } | ||
| + | ||
| + return fileEditor.save(); | ||
| + } | ||
| + | ||
| + boolean saveAllEditors() { | ||
| + FileEditor[] allEditors = getAllEditors(); | ||
| + | ||
| + boolean success = true; | ||
| + for( FileEditor fileEditor : allEditors ) { | ||
| + if( !saveEditor( fileEditor ) ) { | ||
| + success = false; | ||
| + } | ||
| + } | ||
| + | ||
| + return success; | ||
| + } | ||
| + | ||
| + boolean canCloseEditor( FileEditor fileEditor ) { | ||
| + if( !fileEditor.isModified() ) { | ||
| + return true; | ||
| + } | ||
| + | ||
| + Alert alert = getMainWindow().createAlert( AlertType.CONFIRMATION, | ||
| + Messages.get( "FileEditorTabPane.closeAlert.title" ), | ||
| + Messages.get( "FileEditorTabPane.closeAlert.message" ), fileEditor.getTab().getText() ); | ||
| + alert.getButtonTypes().setAll( ButtonType.YES, ButtonType.NO, ButtonType.CANCEL ); | ||
| + | ||
| + ButtonType result = alert.showAndWait().get(); | ||
| + if( result != ButtonType.YES ) { | ||
| + return (result == ButtonType.NO); | ||
| + } | ||
| + | ||
| + return saveEditor( fileEditor ); | ||
| + } | ||
| + | ||
| + boolean closeEditor( FileEditor fileEditor, boolean save ) { | ||
| + if( fileEditor == null ) { | ||
| + return true; | ||
| + } | ||
| + | ||
| + Tab tab = fileEditor.getTab(); | ||
| + | ||
| + if( save ) { | ||
| + Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT ); | ||
| + Event.fireEvent( tab, event ); | ||
| + if( event.isConsumed() ) { | ||
| + return false; | ||
| + } | ||
| + } | ||
| + | ||
| + tabPane.getTabs().remove( tab ); | ||
| + if( tab.getOnClosed() != null ) { | ||
| + Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) ); | ||
| + } | ||
| + | ||
| + return true; | ||
| + } | ||
| + | ||
| + boolean closeAllEditors() { | ||
| + FileEditor[] allEditors = getAllEditors(); | ||
| + FileEditor activeEditor = activeFileEditor.get(); | ||
| + | ||
| + // try to save active tab first because in case the user decides to cancel, | ||
| + // then it stays active | ||
| + if( activeEditor != null && !canCloseEditor( activeEditor ) ) { | ||
| + return false; | ||
| + } | ||
| + | ||
| + // save modified tabs | ||
| + for( int i = 0; i < allEditors.length; i++ ) { | ||
| + FileEditor fileEditor = allEditors[ i ]; | ||
| + if( fileEditor == activeEditor ) { | ||
| + continue; | ||
| + } | ||
| + | ||
| + if( fileEditor.isModified() ) { | ||
| + // activate the modified tab to make its modified content visible to the user | ||
| + tabPane.getSelectionModel().select( i ); | ||
| + | ||
| + if( !canCloseEditor( fileEditor ) ) { | ||
| + return false; | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + // close all tabs | ||
| + for( FileEditor fileEditor : allEditors ) { | ||
| + if( !closeEditor( fileEditor, false ) ) { | ||
| + return false; | ||
| + } | ||
| + } | ||
| + | ||
| + saveState( allEditors, activeEditor ); | ||
| + | ||
| + return tabPane.getTabs().isEmpty(); | ||
| + } | ||
| + | ||
| + private FileEditor[] getAllEditors() { | ||
| + ObservableList<Tab> tabs = tabPane.getTabs(); | ||
| + FileEditor[] allEditors = new FileEditor[ tabs.size() ]; | ||
| + for( int i = 0; i < tabs.size(); i++ ) { | ||
| + allEditors[ i ] = (FileEditor)tabs.get( i ).getUserData(); | ||
| + } | ||
| + return allEditors; | ||
| + } | ||
| + | ||
| + private FileEditor findEditor( Path path ) { | ||
| + for( Tab tab : tabPane.getTabs() ) { | ||
| + FileEditor fileEditor = (FileEditor)tab.getUserData(); | ||
| + if( path.equals( fileEditor.getPath() ) ) { | ||
| + return fileEditor; | ||
| + } | ||
| + } | ||
| + return null; | ||
| + } | ||
| + | ||
| + private FileChooser createFileChooser( String title ) { | ||
| + FileChooser fileChooser = new FileChooser(); | ||
| + fileChooser.setTitle( title ); | ||
| + fileChooser.getExtensionFilters().addAll( | ||
| + new ExtensionFilter( Messages.get( "FileEditorTabPane.chooser.markdownFilesFilter" ), getMarkdownExtensions() ), | ||
| + new ExtensionFilter( Messages.get( "FileEditorTabPane.chooser.allFilesFilter" ), getAllExtensions() ) ); | ||
| + | ||
| + String lastDirectory = MarkdownWriterFXApp.getState().get( "lastDirectory", null ); | ||
| + File file = new File( (lastDirectory != null) ? lastDirectory : "." ); | ||
| + if( !file.isDirectory() ) { | ||
| + file = new File( "." ); | ||
| + } | ||
| + fileChooser.setInitialDirectory( file ); | ||
| + return fileChooser; | ||
| + } | ||
| + | ||
| + private List<String> getMarkdownExtensions() { | ||
| + return coercePropertyList( "application.extensions.markdown", DEFAULT_EXTENSIONS_MARKDOWN ); | ||
| + } | ||
| + | ||
| + private List<String> getAllExtensions() { | ||
| + return coercePropertyList( "application.extensions.all", DEFAULT_EXTENSIONS_ALL ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Convert the generic list of property objects into strings. | ||
| + * | ||
| + * @param property The property value to coerce. | ||
| + * @param defaults The defaults values to use should the property be unset. | ||
| + * | ||
| + * @return The list of properties coerced from objects to strings. | ||
| + */ | ||
| + private List<String> coercePropertyList( String property, List<String> defaults ) { | ||
| + final List<Object> properties = getPropertyList( property, defaults ); | ||
| + | ||
| + return properties.stream() | ||
| + .map( object -> Objects.toString( object, null ) ) | ||
| + .collect( Collectors.toList() ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public String getProperty( String property, String defaultValue ) { | ||
| + return getMainWindow().getProperty( property, defaultValue ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public List<Object> getPropertyList( String property, List<String> defaults ) { | ||
| + return getMainWindow().getPropertyList( property, defaults ); | ||
| + } | ||
| + | ||
| + private void saveLastDirectory( File file ) { | ||
| + MarkdownWriterFXApp.getState().put( "lastDirectory", file.getParent() ); | ||
| + } | ||
| + | ||
| + private void restoreState() { | ||
| + Preferences state = MarkdownWriterFXApp.getState(); | ||
| + String[] fileNames = Utils.getPrefsStrings( state, "file" ); | ||
| + String activeFileName = state.get( "activeFile", null ); | ||
| + | ||
| + int activeIndex = 0; | ||
| + ArrayList<File> files = new ArrayList<>( fileNames.length ); | ||
| + for( String fileName : fileNames ) { | ||
| + File file = new File( fileName ); | ||
| + if( file.exists() ) { | ||
| + files.add( file ); | ||
| + | ||
| + if( fileName.equals( activeFileName ) ) { | ||
| + activeIndex = files.size() - 1; | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + if( files.isEmpty() ) { | ||
| + newEditor(); | ||
| + return; | ||
| + } | ||
| + | ||
| + openEditors( files, activeIndex ); | ||
| + } | ||
| + | ||
| + private void saveState( FileEditor[] allEditors, FileEditor activeEditor ) { | ||
| + ArrayList<String> fileNames = new ArrayList<>( allEditors.length ); | ||
| + for( FileEditor fileEditor : allEditors ) { | ||
| + if( fileEditor.getPath() != null ) { | ||
| + fileNames.add( fileEditor.getPath().toString() ); | ||
| + } | ||
| + } | ||
| + | ||
| + Preferences state = MarkdownWriterFXApp.getState(); | ||
| + Utils.putPrefsStrings( state, "file", fileNames.toArray( new String[ fileNames.size() ] ) ); | ||
| + if( activeEditor != null && activeEditor.getPath() != null ) { | ||
| + state.put( "activeFile", activeEditor.getPath().toString() ); | ||
| + } else { | ||
| + state.remove( "activeFile" ); | ||
| + } | ||
| + } | ||
| } | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
| - | ||
| -package org.markdownwriterfx; | ||
| - | ||
| -import java.text.MessageFormat; | ||
| -import java.util.function.Function; | ||
| -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.event.Event; | ||
| -import javafx.scene.Node; | ||
| -import javafx.scene.Scene; | ||
| -import javafx.scene.control.Alert; | ||
| -import javafx.scene.control.Alert.AlertType; | ||
| -import javafx.scene.image.Image; | ||
| -import javafx.scene.image.ImageView; | ||
| -import javafx.scene.control.Menu; | ||
| -import javafx.scene.control.MenuBar; | ||
| -import javafx.scene.control.ToolBar; | ||
| -import javafx.scene.input.KeyCode; | ||
| -import javafx.scene.input.KeyEvent; | ||
| -import javafx.scene.layout.BorderPane; | ||
| -import javafx.scene.layout.VBox; | ||
| -import javafx.stage.Window; | ||
| -import javafx.stage.WindowEvent; | ||
| -import org.markdownwriterfx.editor.MarkdownEditorPane; | ||
| -import org.markdownwriterfx.options.OptionsDialog; | ||
| -import org.markdownwriterfx.util.Action; | ||
| -import org.markdownwriterfx.util.ActionUtils; | ||
| -import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*; | ||
| - | ||
| -/** | ||
| - * Main window containing a tab pane in the center for file editors. | ||
| - * | ||
| - * @author Karl Tauber | ||
| - */ | ||
| -class MainWindow | ||
| -{ | ||
| - private final Scene scene; | ||
| - private final FileEditorTabPane fileEditorTabPane; | ||
| - private MenuBar menuBar; | ||
| - | ||
| - MainWindow() { | ||
| - fileEditorTabPane = new FileEditorTabPane(this); | ||
| - | ||
| - BorderPane borderPane = new BorderPane(); | ||
| - borderPane.setPrefSize(800, 800); | ||
| - borderPane.setTop(createMenuBarAndToolBar()); | ||
| - borderPane.setCenter(fileEditorTabPane.getNode()); | ||
| - | ||
| - scene = new Scene(borderPane); | ||
| - scene.getStylesheets().add("org/markdownwriterfx/MarkdownWriter.css"); | ||
| - scene.windowProperty().addListener((observable, oldWindow, newWindow) -> { | ||
| - newWindow.setOnCloseRequest(e -> { | ||
| - if (!fileEditorTabPane.closeAllEditors()) | ||
| - e.consume(); | ||
| - }); | ||
| - | ||
| - // workaround for a bug in JavaFX: unselect menubar if window looses focus | ||
| - newWindow.focusedProperty().addListener((obs, oldFocused, newFocused) -> { | ||
| - if (!newFocused) { | ||
| - // send an ESC key event to the menubar | ||
| - menuBar.fireEvent(new KeyEvent(KeyEvent.KEY_PRESSED, | ||
| - KeyEvent.CHAR_UNDEFINED, "", KeyCode.ESCAPE, | ||
| - false, false, false, false)); | ||
| - } | ||
| - }); | ||
| - }); | ||
| - } | ||
| - | ||
| - Scene getScene() { | ||
| - return scene; | ||
| - } | ||
| - | ||
| - private Node createMenuBarAndToolBar() { | ||
| - BooleanBinding activeFileEditorIsNull = fileEditorTabPane.activeFileEditorProperty().isNull(); | ||
| - | ||
| - // File actions | ||
| - Action fileNewAction = new Action(Messages.get("MainWindow.fileNewAction"), "Shortcut+N", FILE_ALT, e -> fileNew()); | ||
| - Action fileOpenAction = new Action(Messages.get("MainWindow.fileOpenAction"), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen()); | ||
| - Action fileCloseAction = new Action(Messages.get("MainWindow.fileCloseAction"), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull); | ||
| - Action fileCloseAllAction = new Action(Messages.get("MainWindow.fileCloseAllAction"), null, null, e -> fileCloseAll(), activeFileEditorIsNull); | ||
| - Action fileSaveAction = new Action(Messages.get("MainWindow.fileSaveAction"), "Shortcut+S", FLOPPY_ALT, e -> fileSave(), | ||
| - createActiveBooleanProperty(FileEditor::modifiedProperty).not()); | ||
| - Action fileSaveAllAction = new Action(Messages.get("MainWindow.fileSaveAllAction"), "Shortcut+Shift+S", null, e -> fileSaveAll(), | ||
| - Bindings.not(fileEditorTabPane.anyFileEditorModifiedProperty())); | ||
| - Action fileExitAction = new Action(Messages.get("MainWindow.fileExitAction"), null, null, e -> fileExit()); | ||
| - | ||
| - // Edit actions | ||
| - Action editUndoAction = new Action(Messages.get("MainWindow.editUndoAction"), "Shortcut+Z", UNDO, | ||
| - e -> getActiveEditor().undo(), | ||
| - createActiveBooleanProperty(FileEditor::canUndoProperty).not()); | ||
| - Action editRedoAction = new Action(Messages.get("MainWindow.editRedoAction"), "Shortcut+Y", REPEAT, | ||
| - e -> getActiveEditor().redo(), | ||
| - createActiveBooleanProperty(FileEditor::canRedoProperty).not()); | ||
| - | ||
| - // Insert actions | ||
| - Action insertBoldAction = new Action(Messages.get("MainWindow.insertBoldAction"), "Shortcut+B", BOLD, | ||
| - e -> getActiveEditor().surroundSelection("**", "**"), | ||
| - activeFileEditorIsNull); | ||
| - Action insertItalicAction = new Action(Messages.get("MainWindow.insertItalicAction"), "Shortcut+I", ITALIC, | ||
| - e -> getActiveEditor().surroundSelection("*", "*"), | ||
| - activeFileEditorIsNull); | ||
| - Action insertStrikethroughAction = new Action(Messages.get("MainWindow.insertStrikethroughAction"), "Shortcut+T", STRIKETHROUGH, | ||
| - e -> getActiveEditor().surroundSelection("~~", "~~"), | ||
| - activeFileEditorIsNull); | ||
| - Action insertBlockquoteAction = new Action(Messages.get("MainWindow.insertBlockquoteAction"), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac | ||
| - e -> getActiveEditor().surroundSelection("\n\n> ", ""), | ||
| - activeFileEditorIsNull); | ||
| - Action insertCodeAction = new Action(Messages.get("MainWindow.insertCodeAction"), "Shortcut+K", CODE, | ||
| - e -> getActiveEditor().surroundSelection("`", "`"), | ||
| - activeFileEditorIsNull); | ||
| - Action insertFencedCodeBlockAction = new Action(Messages.get("MainWindow.insertFencedCodeBlockAction"), "Shortcut+Shift+K", FILE_CODE_ALT, | ||
| - e -> getActiveEditor().surroundSelection("\n\n```\n", "\n```\n\n", Messages.get("MainWindow.insertFencedCodeBlockText")), | ||
| - activeFileEditorIsNull); | ||
| - | ||
| - Action insertLinkAction = new Action(Messages.get("MainWindow.insertLinkAction"), "Shortcut+L", LINK, | ||
| - e -> getActiveEditor().insertLink(), | ||
| - activeFileEditorIsNull); | ||
| - Action insertImageAction = new Action(Messages.get("MainWindow.insertImageAction"), "Shortcut+G", PICTURE_ALT, | ||
| - e -> getActiveEditor().insertImage(), | ||
| - activeFileEditorIsNull); | ||
| - | ||
| - Action insertHeader1Action = new Action(Messages.get("MainWindow.insertHeader1Action"), "Shortcut+1", HEADER, | ||
| - e -> getActiveEditor().surroundSelection("\n\n# ", "", Messages.get("MainWindow.insertHeader1Text")), | ||
| - activeFileEditorIsNull); | ||
| - Action insertHeader2Action = new Action(Messages.get("MainWindow.insertHeader2Action"), "Shortcut+2", HEADER, | ||
| - e -> getActiveEditor().surroundSelection("\n\n## ", "", Messages.get("MainWindow.insertHeader2Text")), | ||
| - activeFileEditorIsNull); | ||
| - Action insertHeader3Action = new Action(Messages.get("MainWindow.insertHeader3Action"), "Shortcut+3", HEADER, | ||
| - e -> getActiveEditor().surroundSelection("\n\n### ", "", Messages.get("MainWindow.insertHeader3Text")), | ||
| - activeFileEditorIsNull); | ||
| - Action insertHeader4Action = new Action(Messages.get("MainWindow.insertHeader4Action"), "Shortcut+4", HEADER, | ||
| - e -> getActiveEditor().surroundSelection("\n\n#### ", "", Messages.get("MainWindow.insertHeader4Text")), | ||
| - activeFileEditorIsNull); | ||
| - Action insertHeader5Action = new Action(Messages.get("MainWindow.insertHeader5Action"), "Shortcut+5", HEADER, | ||
| - e -> getActiveEditor().surroundSelection("\n\n##### ", "", Messages.get("MainWindow.insertHeader5Text")), | ||
| - activeFileEditorIsNull); | ||
| - Action insertHeader6Action = new Action(Messages.get("MainWindow.insertHeader6Action"), "Shortcut+6", HEADER, | ||
| - e -> getActiveEditor().surroundSelection("\n\n###### ", "", Messages.get("MainWindow.insertHeader6Text")), | ||
| - activeFileEditorIsNull); | ||
| - | ||
| - Action insertUnorderedListAction = new Action(Messages.get("MainWindow.insertUnorderedListAction"), "Shortcut+U", LIST_UL, | ||
| - e -> getActiveEditor().surroundSelection("\n\n* ", ""), | ||
| - activeFileEditorIsNull); | ||
| - Action insertOrderedListAction = new Action(Messages.get("MainWindow.insertOrderedListAction"), "Shortcut+Shift+O", LIST_OL, | ||
| - e -> getActiveEditor().surroundSelection("\n\n1. ", ""), | ||
| - activeFileEditorIsNull); | ||
| - Action insertHorizontalRuleAction = new Action(Messages.get("MainWindow.insertHorizontalRuleAction"), "Shortcut+H", null, | ||
| - e -> getActiveEditor().surroundSelection("\n\n---\n\n", ""), | ||
| - activeFileEditorIsNull); | ||
| - | ||
| - // Tools actions | ||
| - Action toolsOptionsAction = new Action(Messages.get("MainWindow.toolsOptionsAction"), "Shortcut+,", null, e -> toolsOptions()); | ||
| - | ||
| - // Help actions | ||
| - Action helpAboutAction = new Action(Messages.get("MainWindow.helpAboutAction"), null, null, e -> helpAbout()); | ||
| - | ||
| - | ||
| - //---- MenuBar ---- | ||
| - | ||
| - Menu fileMenu = ActionUtils.createMenu(Messages.get("MainWindow.fileMenu"), | ||
| - fileNewAction, | ||
| - fileOpenAction, | ||
| - null, | ||
| - fileCloseAction, | ||
| - fileCloseAllAction, | ||
| - null, | ||
| - fileSaveAction, | ||
| - fileSaveAllAction, | ||
| - null, | ||
| - fileExitAction); | ||
| - | ||
| - Menu editMenu = ActionUtils.createMenu(Messages.get("MainWindow.editMenu"), | ||
| - editUndoAction, | ||
| - editRedoAction); | ||
| - | ||
| - Menu insertMenu = ActionUtils.createMenu(Messages.get("MainWindow.insertMenu"), | ||
| - insertBoldAction, | ||
| - insertItalicAction, | ||
| - insertStrikethroughAction, | ||
| - insertBlockquoteAction, | ||
| - insertCodeAction, | ||
| - insertFencedCodeBlockAction, | ||
| - null, | ||
| - insertLinkAction, | ||
| - insertImageAction, | ||
| - null, | ||
| - insertHeader1Action, | ||
| - insertHeader2Action, | ||
| - insertHeader3Action, | ||
| - insertHeader4Action, | ||
| - insertHeader5Action, | ||
| - insertHeader6Action, | ||
| - null, | ||
| - insertUnorderedListAction, | ||
| - insertOrderedListAction, | ||
| - insertHorizontalRuleAction); | ||
| - | ||
| - Menu toolsMenu = ActionUtils.createMenu(Messages.get("MainWindow.toolsMenu"), | ||
| - toolsOptionsAction); | ||
| - | ||
| - Menu helpMenu = ActionUtils.createMenu(Messages.get("MainWindow.helpMenu"), | ||
| - helpAboutAction); | ||
| - | ||
| - menuBar = new MenuBar(fileMenu, editMenu, insertMenu, toolsMenu, helpMenu); | ||
| - | ||
| - | ||
| - //---- ToolBar ---- | ||
| - | ||
| - ToolBar toolBar = ActionUtils.createToolBar( | ||
| - fileNewAction, | ||
| - fileOpenAction, | ||
| - fileSaveAction, | ||
| - null, | ||
| - editUndoAction, | ||
| - editRedoAction, | ||
| - null, | ||
| - insertBoldAction, | ||
| - insertItalicAction, | ||
| - insertBlockquoteAction, | ||
| - insertCodeAction, | ||
| - insertFencedCodeBlockAction, | ||
| - null, | ||
| - insertLinkAction, | ||
| - insertImageAction, | ||
| - null, | ||
| - insertHeader1Action, | ||
| - null, | ||
| - insertUnorderedListAction, | ||
| - insertOrderedListAction); | ||
| - | ||
| - return new VBox(menuBar, toolBar); | ||
| - } | ||
| - | ||
| - private MarkdownEditorPane getActiveEditor() { | ||
| - return fileEditorTabPane.getActiveFileEditor().getEditor(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates a boolean property that is bound to another boolean value | ||
| - * of the active editor. | ||
| - */ | ||
| - private BooleanProperty createActiveBooleanProperty(Function<FileEditor, ObservableBooleanValue> func) { | ||
| - BooleanProperty b = new SimpleBooleanProperty(); | ||
| - FileEditor fileEditor = fileEditorTabPane.getActiveFileEditor(); | ||
| - if (fileEditor != null) | ||
| - b.bind(func.apply(fileEditor)); | ||
| - fileEditorTabPane.activeFileEditorProperty().addListener((observable, oldFileEditor, newFileEditor) -> { | ||
| - b.unbind(); | ||
| - if (newFileEditor != null) | ||
| - b.bind(func.apply(newFileEditor)); | ||
| - else | ||
| - b.set(false); | ||
| - }); | ||
| - return b; | ||
| - } | ||
| - | ||
| - Alert createAlert(AlertType alertType, String title, | ||
| - String contentTextFormat, Object... contentTextArgs) | ||
| - { | ||
| - Alert alert = new Alert(alertType); | ||
| - alert.setTitle(title); | ||
| - alert.setHeaderText(null); | ||
| - alert.setContentText(MessageFormat.format(contentTextFormat, contentTextArgs)); | ||
| - alert.initOwner(getScene().getWindow()); | ||
| - return alert; | ||
| - } | ||
| - | ||
| - //---- File actions ------------------------------------------------------- | ||
| - | ||
| - private void fileNew() { | ||
| - fileEditorTabPane.newEditor(); | ||
| - } | ||
| - | ||
| - private void fileOpen() { | ||
| - fileEditorTabPane.openEditor(); | ||
| - } | ||
| - | ||
| - private void fileClose() { | ||
| - fileEditorTabPane.closeEditor(fileEditorTabPane.getActiveFileEditor(), true); | ||
| - } | ||
| - | ||
| - private void fileCloseAll() { | ||
| - fileEditorTabPane.closeAllEditors(); | ||
| - } | ||
| - | ||
| - private void fileSave() { | ||
| - fileEditorTabPane.saveEditor(fileEditorTabPane.getActiveFileEditor()); | ||
| - } | ||
| - | ||
| - private void fileSaveAll() { | ||
| - fileEditorTabPane.saveAllEditors(); | ||
| - } | ||
| - | ||
| - private void fileExit() { | ||
| - Window window = scene.getWindow(); | ||
| - Event.fireEvent(window, new WindowEvent(window, WindowEvent.WINDOW_CLOSE_REQUEST)); | ||
| - } | ||
| - | ||
| - //---- Tools actions ------------------------------------------------------ | ||
| - | ||
| - private void toolsOptions() { | ||
| - OptionsDialog dialog = new OptionsDialog(getScene().getWindow()); | ||
| - dialog.showAndWait(); | ||
| - } | ||
| - | ||
| - //---- Help actions ------------------------------------------------------- | ||
| - | ||
| - private void helpAbout() { | ||
| - Alert alert = new Alert(AlertType.INFORMATION); | ||
| - alert.setTitle(Messages.get("MainWindow.about.title")); | ||
| - alert.setHeaderText(Messages.get("MainWindow.about.headerText")); | ||
| - alert.setContentText(Messages.get("MainWindow.about.contentText")); | ||
| - alert.setGraphic(new ImageView(new Image("org/markdownwriterfx/markdownwriterfx32.png"))); | ||
| - alert.initOwner(getScene().getWindow()); | ||
| - | ||
| - alert.showAndWait(); | ||
| - } | ||
| +package org.markdownwriterfx; | ||
| + | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.BOLD; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.CODE; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_ALT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FILE_CODE_ALT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FLOPPY_ALT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.FOLDER_OPEN_ALT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.HEADER; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.ITALIC; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LINK; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_OL; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.LIST_UL; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.PICTURE_ALT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.QUOTE_LEFT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.REPEAT; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.STRIKETHROUGH; | ||
| +import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.UNDO; | ||
| +import java.text.MessageFormat; | ||
| +import java.util.List; | ||
| +import java.util.function.Function; | ||
| +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.event.Event; | ||
| +import javafx.scene.Node; | ||
| +import javafx.scene.Scene; | ||
| +import javafx.scene.control.Alert; | ||
| +import javafx.scene.control.Alert.AlertType; | ||
| +import javafx.scene.control.Menu; | ||
| +import javafx.scene.control.MenuBar; | ||
| +import javafx.scene.control.ToolBar; | ||
| +import javafx.scene.image.Image; | ||
| +import javafx.scene.image.ImageView; | ||
| +import static javafx.scene.input.KeyCode.ESCAPE; | ||
| +import javafx.scene.input.KeyEvent; | ||
| +import static javafx.scene.input.KeyEvent.CHAR_UNDEFINED; | ||
| +import static javafx.scene.input.KeyEvent.KEY_PRESSED; | ||
| +import javafx.scene.layout.BorderPane; | ||
| +import javafx.scene.layout.VBox; | ||
| +import javafx.stage.Window; | ||
| +import javafx.stage.WindowEvent; | ||
| +import org.markdownwriterfx.editor.MarkdownEditorPane; | ||
| +import org.markdownwriterfx.options.OptionsDialog; | ||
| +import org.markdownwriterfx.util.Action; | ||
| +import org.markdownwriterfx.util.ActionUtils; | ||
| + | ||
| +/** | ||
| + * Main window containing a tab pane in the center for file editors. | ||
| + * | ||
| + * @author Karl Tauber | ||
| + */ | ||
| +class MainWindow implements ApplicationProperty { | ||
| + | ||
| + private final Scene scene; | ||
| + private final FileEditorTabPane fileEditorTabPane; | ||
| + private MenuBar menuBar; | ||
| + private final ApplicationProperty application; | ||
| + | ||
| + MainWindow( ApplicationProperty application ) { | ||
| + this.application = application; | ||
| + this.fileEditorTabPane = new FileEditorTabPane( this ); | ||
| + | ||
| + BorderPane borderPane = new BorderPane(); | ||
| + borderPane.setPrefSize( 800, 800 ); | ||
| + borderPane.setTop( createMenuBarAndToolBar() ); | ||
| + borderPane.setCenter( fileEditorTabPane.getNode() ); | ||
| + | ||
| + this.scene = new Scene( borderPane ); | ||
| + this.scene.getStylesheets().add( "org/markdownwriterfx/MarkdownWriter.css" ); | ||
| + this.scene.windowProperty().addListener( (observable, oldWindow, newWindow) -> { | ||
| + newWindow.setOnCloseRequest( e -> { | ||
| + if( !this.fileEditorTabPane.closeAllEditors() ) { | ||
| + e.consume(); | ||
| + } | ||
| + } ); | ||
| + | ||
| + // workaround for a bug in JavaFX: unselect menubar if window looses focus | ||
| + newWindow.focusedProperty().addListener( (obs, oldFocused, newFocused) -> { | ||
| + if( !newFocused ) { | ||
| + // send an ESC key event to the menubar | ||
| + this.menuBar.fireEvent( new KeyEvent( KEY_PRESSED, CHAR_UNDEFINED, "", ESCAPE, | ||
| + false, false, false, false ) ); | ||
| + } | ||
| + } ); | ||
| + } ); | ||
| + | ||
| + } | ||
| + | ||
| + /** | ||
| + * Delegates resolving the property to the application. | ||
| + * | ||
| + * @param property The property to obtain. | ||
| + * @param defaultValue The default value should no value be set. | ||
| + * | ||
| + * @return The value associated with the given property, or the defaultValue | ||
| + * if the property hasn't been set. | ||
| + */ | ||
| + @Override | ||
| + public String getProperty( String property, String defaultValue ) { | ||
| + return getApplicationProperty().getProperty( property, defaultValue ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public List<Object> getPropertyList(String property, List<String> defaults ) { | ||
| + return getApplicationProperty().getPropertyList( property, defaults ); | ||
| + } | ||
| + | ||
| + private ApplicationProperty getApplicationProperty() { | ||
| + return this.application; | ||
| + } | ||
| + | ||
| + Scene getScene() { | ||
| + return scene; | ||
| + } | ||
| + | ||
| + private Node createMenuBarAndToolBar() { | ||
| + BooleanBinding activeFileEditorIsNull = fileEditorTabPane.activeFileEditorProperty().isNull(); | ||
| + | ||
| + // File actions | ||
| + Action fileNewAction = new Action( Messages.get( "MainWindow.fileNewAction" ), "Shortcut+N", FILE_ALT, e -> fileNew() ); | ||
| + Action fileOpenAction = new Action( Messages.get( "MainWindow.fileOpenAction" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() ); | ||
| + Action fileCloseAction = new Action( Messages.get( "MainWindow.fileCloseAction" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull ); | ||
| + Action fileCloseAllAction = new Action( Messages.get( "MainWindow.fileCloseAllAction" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull ); | ||
| + Action fileSaveAction = new Action( Messages.get( "MainWindow.fileSaveAction" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(), | ||
| + createActiveBooleanProperty( FileEditor::modifiedProperty ).not() ); | ||
| + Action fileSaveAllAction = new Action( Messages.get( "MainWindow.fileSaveAllAction" ), "Shortcut+Shift+S", null, e -> fileSaveAll(), | ||
| + Bindings.not( fileEditorTabPane.anyFileEditorModifiedProperty() ) ); | ||
| + Action fileExitAction = new Action( Messages.get( "MainWindow.fileExitAction" ), null, null, e -> fileExit() ); | ||
| + | ||
| + // Edit actions | ||
| + Action editUndoAction = new Action( Messages.get( "MainWindow.editUndoAction" ), "Shortcut+Z", UNDO, | ||
| + e -> getActiveEditor().undo(), | ||
| + createActiveBooleanProperty( FileEditor::canUndoProperty ).not() ); | ||
| + Action editRedoAction = new Action( Messages.get( "MainWindow.editRedoAction" ), "Shortcut+Y", REPEAT, | ||
| + e -> getActiveEditor().redo(), | ||
| + createActiveBooleanProperty( FileEditor::canRedoProperty ).not() ); | ||
| + | ||
| + // Insert actions | ||
| + Action insertBoldAction = new Action( Messages.get( "MainWindow.insertBoldAction" ), "Shortcut+B", BOLD, | ||
| + e -> getActiveEditor().surroundSelection( "**", "**" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertItalicAction = new Action( Messages.get( "MainWindow.insertItalicAction" ), "Shortcut+I", ITALIC, | ||
| + e -> getActiveEditor().surroundSelection( "*", "*" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertStrikethroughAction = new Action( Messages.get( "MainWindow.insertStrikethroughAction" ), "Shortcut+T", STRIKETHROUGH, | ||
| + e -> getActiveEditor().surroundSelection( "~~", "~~" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertBlockquoteAction = new Action( Messages.get( "MainWindow.insertBlockquoteAction" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac | ||
| + e -> getActiveEditor().surroundSelection( "\n\n> ", "" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertCodeAction = new Action( Messages.get( "MainWindow.insertCodeAction" ), "Shortcut+K", CODE, | ||
| + e -> getActiveEditor().surroundSelection( "`", "`" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertFencedCodeBlockAction = new Action( Messages.get( "MainWindow.insertFencedCodeBlockAction" ), "Shortcut+Shift+K", FILE_CODE_ALT, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", Messages.get( "MainWindow.insertFencedCodeBlockText" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + | ||
| + Action insertLinkAction = new Action( Messages.get( "MainWindow.insertLinkAction" ), "Shortcut+L", LINK, | ||
| + e -> getActiveEditor().insertLink(), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertImageAction = new Action( Messages.get( "MainWindow.insertImageAction" ), "Shortcut+G", PICTURE_ALT, | ||
| + e -> getActiveEditor().insertImage(), | ||
| + activeFileEditorIsNull ); | ||
| + | ||
| + Action insertHeader1Action = new Action( Messages.get( "MainWindow.insertHeader1Action" ), "Shortcut+1", HEADER, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n# ", "", Messages.get( "MainWindow.insertHeader1Text" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertHeader2Action = new Action( Messages.get( "MainWindow.insertHeader2Action" ), "Shortcut+2", HEADER, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n## ", "", Messages.get( "MainWindow.insertHeader2Text" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertHeader3Action = new Action( Messages.get( "MainWindow.insertHeader3Action" ), "Shortcut+3", HEADER, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n### ", "", Messages.get( "MainWindow.insertHeader3Text" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertHeader4Action = new Action( Messages.get( "MainWindow.insertHeader4Action" ), "Shortcut+4", HEADER, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n#### ", "", Messages.get( "MainWindow.insertHeader4Text" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertHeader5Action = new Action( Messages.get( "MainWindow.insertHeader5Action" ), "Shortcut+5", HEADER, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n##### ", "", Messages.get( "MainWindow.insertHeader5Text" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertHeader6Action = new Action( Messages.get( "MainWindow.insertHeader6Action" ), "Shortcut+6", HEADER, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n###### ", "", Messages.get( "MainWindow.insertHeader6Text" ) ), | ||
| + activeFileEditorIsNull ); | ||
| + | ||
| + Action insertUnorderedListAction = new Action( Messages.get( "MainWindow.insertUnorderedListAction" ), "Shortcut+U", LIST_UL, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n* ", "" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertOrderedListAction = new Action( Messages.get( "MainWindow.insertOrderedListAction" ), "Shortcut+Shift+O", LIST_OL, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ), | ||
| + activeFileEditorIsNull ); | ||
| + Action insertHorizontalRuleAction = new Action( Messages.get( "MainWindow.insertHorizontalRuleAction" ), "Shortcut+H", null, | ||
| + e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ), | ||
| + activeFileEditorIsNull ); | ||
| + | ||
| + // Tools actions | ||
| + Action toolsOptionsAction = new Action( Messages.get( "MainWindow.toolsOptionsAction" ), "Shortcut+,", null, e -> toolsOptions() ); | ||
| + | ||
| + // Help actions | ||
| + Action helpAboutAction = new Action( Messages.get( "MainWindow.helpAboutAction" ), null, null, e -> helpAbout() ); | ||
| + | ||
| + //---- MenuBar ---- | ||
| + Menu fileMenu = ActionUtils.createMenu( Messages.get( "MainWindow.fileMenu" ), | ||
| + fileNewAction, | ||
| + fileOpenAction, | ||
| + null, | ||
| + fileCloseAction, | ||
| + fileCloseAllAction, | ||
| + null, | ||
| + fileSaveAction, | ||
| + fileSaveAllAction, | ||
| + null, | ||
| + fileExitAction ); | ||
| + | ||
| + Menu editMenu = ActionUtils.createMenu( Messages.get( "MainWindow.editMenu" ), | ||
| + editUndoAction, | ||
| + editRedoAction ); | ||
| + | ||
| + Menu insertMenu = ActionUtils.createMenu( Messages.get( "MainWindow.insertMenu" ), | ||
| + insertBoldAction, | ||
| + insertItalicAction, | ||
| + insertStrikethroughAction, | ||
| + insertBlockquoteAction, | ||
| + insertCodeAction, | ||
| + insertFencedCodeBlockAction, | ||
| + null, | ||
| + insertLinkAction, | ||
| + insertImageAction, | ||
| + null, | ||
| + insertHeader1Action, | ||
| + insertHeader2Action, | ||
| + insertHeader3Action, | ||
| + insertHeader4Action, | ||
| + insertHeader5Action, | ||
| + insertHeader6Action, | ||
| + null, | ||
| + insertUnorderedListAction, | ||
| + insertOrderedListAction, | ||
| + insertHorizontalRuleAction ); | ||
| + | ||
| + Menu toolsMenu = ActionUtils.createMenu( Messages.get( "MainWindow.toolsMenu" ), | ||
| + toolsOptionsAction ); | ||
| + | ||
| + Menu helpMenu = ActionUtils.createMenu( Messages.get( "MainWindow.helpMenu" ), | ||
| + helpAboutAction ); | ||
| + | ||
| + menuBar = new MenuBar( fileMenu, editMenu, insertMenu, toolsMenu, helpMenu ); | ||
| + | ||
| + //---- ToolBar ---- | ||
| + ToolBar toolBar = ActionUtils.createToolBar( | ||
| + fileNewAction, | ||
| + fileOpenAction, | ||
| + fileSaveAction, | ||
| + null, | ||
| + editUndoAction, | ||
| + editRedoAction, | ||
| + null, | ||
| + insertBoldAction, | ||
| + insertItalicAction, | ||
| + insertBlockquoteAction, | ||
| + insertCodeAction, | ||
| + insertFencedCodeBlockAction, | ||
| + null, | ||
| + insertLinkAction, | ||
| + insertImageAction, | ||
| + null, | ||
| + insertHeader1Action, | ||
| + null, | ||
| + insertUnorderedListAction, | ||
| + insertOrderedListAction ); | ||
| + | ||
| + return new VBox( menuBar, toolBar ); | ||
| + } | ||
| + | ||
| + private MarkdownEditorPane getActiveEditor() { | ||
| + return fileEditorTabPane.getActiveFileEditor().getEditor(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates a boolean property that is bound to another boolean value of the | ||
| + * active editor. | ||
| + */ | ||
| + private BooleanProperty createActiveBooleanProperty( Function<FileEditor, ObservableBooleanValue> func ) { | ||
| + BooleanProperty b = new SimpleBooleanProperty(); | ||
| + FileEditor fileEditor = fileEditorTabPane.getActiveFileEditor(); | ||
| + if( fileEditor != null ) { | ||
| + b.bind( func.apply( fileEditor ) ); | ||
| + } | ||
| + fileEditorTabPane.activeFileEditorProperty().addListener( (observable, oldFileEditor, newFileEditor) -> { | ||
| + b.unbind(); | ||
| + if( newFileEditor != null ) { | ||
| + b.bind( func.apply( newFileEditor ) ); | ||
| + } else { | ||
| + b.set( false ); | ||
| + } | ||
| + } ); | ||
| + return b; | ||
| + } | ||
| + | ||
| + Alert createAlert( AlertType alertType, String title, | ||
| + String contentTextFormat, Object... contentTextArgs ) { | ||
| + Alert alert = new Alert( alertType ); | ||
| + alert.setTitle( title ); | ||
| + alert.setHeaderText( null ); | ||
| + alert.setContentText( MessageFormat.format( contentTextFormat, contentTextArgs ) ); | ||
| + alert.initOwner( getScene().getWindow() ); | ||
| + return alert; | ||
| + } | ||
| + | ||
| + //---- File actions ------------------------------------------------------- | ||
| + private void fileNew() { | ||
| + fileEditorTabPane.newEditor(); | ||
| + } | ||
| + | ||
| + private void fileOpen() { | ||
| + fileEditorTabPane.openEditor(); | ||
| + } | ||
| + | ||
| + private void fileClose() { | ||
| + fileEditorTabPane.closeEditor( fileEditorTabPane.getActiveFileEditor(), true ); | ||
| + } | ||
| + | ||
| + private void fileCloseAll() { | ||
| + fileEditorTabPane.closeAllEditors(); | ||
| + } | ||
| + | ||
| + private void fileSave() { | ||
| + fileEditorTabPane.saveEditor( fileEditorTabPane.getActiveFileEditor() ); | ||
| + } | ||
| + | ||
| + private void fileSaveAll() { | ||
| + fileEditorTabPane.saveAllEditors(); | ||
| + } | ||
| + | ||
| + private void fileExit() { | ||
| + Window window = scene.getWindow(); | ||
| + Event.fireEvent( window, new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) ); | ||
| + } | ||
| + | ||
| + //---- Tools actions ------------------------------------------------------ | ||
| + private void toolsOptions() { | ||
| + OptionsDialog dialog = new OptionsDialog( getScene().getWindow() ); | ||
| + dialog.showAndWait(); | ||
| + } | ||
| + | ||
| + //---- Help actions ------------------------------------------------------- | ||
| + private void helpAbout() { | ||
| + Alert alert = new Alert( AlertType.INFORMATION ); | ||
| + alert.setTitle( Messages.get( "MainWindow.about.title" ) ); | ||
| + alert.setHeaderText( Messages.get( "MainWindow.about.headerText" ) ); | ||
| + alert.setContentText( Messages.get( "MainWindow.about.contentText" ) ); | ||
| + alert.setGraphic( new ImageView( new Image( "org/markdownwriterfx/markdownwriterfx32.png" ) ) ); | ||
| + alert.initOwner( getScene().getWindow() ); | ||
| + | ||
| + alert.showAndWait(); | ||
| + } | ||
| } | ||
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| */ | ||
| - | ||
| package org.markdownwriterfx; | ||
| +import java.io.IOException; | ||
| +import java.net.URISyntaxException; | ||
| +import java.net.URL; | ||
| +import java.nio.file.Path; | ||
| +import static java.nio.file.Paths.get; | ||
| +import java.util.List; | ||
| import java.util.prefs.Preferences; | ||
| +import static java.util.prefs.Preferences.userRoot; | ||
| import javafx.application.Application; | ||
| +import javafx.scene.Scene; | ||
| import javafx.scene.image.Image; | ||
| import javafx.stage.Stage; | ||
| +import org.apache.commons.configuration.ConfigurationException; | ||
| +import org.apache.commons.configuration.PropertiesConfiguration; | ||
| import org.markdownwriterfx.options.Options; | ||
| import org.markdownwriterfx.util.StageState; | ||
| /** | ||
| * Markdown Writer FX application. | ||
| * | ||
| * @author Karl Tauber | ||
| */ | ||
| -public class MarkdownWriterFXApp | ||
| - extends Application | ||
| -{ | ||
| - private static Application app; | ||
| +public final class MarkdownWriterFXApp extends Application implements ApplicationProperty { | ||
| - private MainWindow mainWindow; | ||
| - @SuppressWarnings("unused") | ||
| - private StageState stageState; | ||
| + private static Application app; | ||
| - public static void main(String[] args) { | ||
| - launch(args); | ||
| - } | ||
| + private MainWindow mainWindow; | ||
| + private StageState stageState; | ||
| + private PropertiesConfiguration configuration; | ||
| - @Override | ||
| - public void start(Stage primaryStage) throws Exception { | ||
| - app = this; | ||
| - Options.load(getOptions()); | ||
| + public static void main( String[] args ) { | ||
| + launch( args ); | ||
| + } | ||
| - mainWindow = new MainWindow(); | ||
| + /** | ||
| + * Application entry point. | ||
| + * | ||
| + * @param stage The primary application stage. | ||
| + * | ||
| + * @throws Exception Could not read configuration file. | ||
| + */ | ||
| + @Override | ||
| + public void start( Stage stage ) throws Exception { | ||
| + initApplication(); | ||
| + initConfiguration(); | ||
| + initOptions(); | ||
| + initWindow(); | ||
| + initState( stage ); | ||
| + initStage( stage ); | ||
| - stageState = new StageState(primaryStage, getState()); | ||
| + stage.show(); | ||
| + } | ||
| - primaryStage.getIcons().addAll( | ||
| - new Image("org/markdownwriterfx/markdownwriterfx16.png"), | ||
| - new Image("org/markdownwriterfx/markdownwriterfx32.png"), | ||
| - new Image("org/markdownwriterfx/markdownwriterfx128.png"), | ||
| - new Image("org/markdownwriterfx/markdownwriterfx256.png"), | ||
| - new Image("org/markdownwriterfx/markdownwriterfx512.png")); | ||
| - primaryStage.setTitle("Markdown Writer FX"); | ||
| - primaryStage.setScene(mainWindow.getScene()); | ||
| - primaryStage.show(); | ||
| - } | ||
| + public PropertiesConfiguration getConfiguration() { | ||
| + return this.configuration; | ||
| + } | ||
| - public static void showDocument(String uri) { | ||
| - app.getHostServices().showDocument(uri); | ||
| - } | ||
| + private String getApplicationTitle() { | ||
| + return getProperty( "application.title", "Markdown Writer FX" ); | ||
| + } | ||
| - static private Preferences getPrefsRoot() { | ||
| - return Preferences.userRoot().node("markdownwriterfx"); | ||
| - } | ||
| + @Override | ||
| + public String getProperty( String property, String defaultValue ) { | ||
| + return getConfiguration().getString( property, defaultValue ); | ||
| + } | ||
| + | ||
| + @Override | ||
| + public List<Object> getPropertyList(String property, List<String> defaults ) { | ||
| + return getConfiguration().getList( property ); | ||
| + } | ||
| - static Preferences getOptions() { | ||
| - return getPrefsRoot().node("options"); | ||
| - } | ||
| + private void initApplication() { | ||
| + app = this; | ||
| + } | ||
| - public static Preferences getState() { | ||
| - return getPrefsRoot().node("state"); | ||
| - } | ||
| + protected void initConfiguration() | ||
| + throws ConfigurationException, URISyntaxException, IOException { | ||
| + setConfiguration( createPropertiesConfiguration() ); | ||
| + } | ||
| + | ||
| + private PropertiesConfiguration createPropertiesConfiguration() | ||
| + throws ConfigurationException { | ||
| + final URL url = getConfigurationSource(); | ||
| + | ||
| + return url == null | ||
| + ? new PropertiesConfiguration() | ||
| + : new PropertiesConfiguration( url ); | ||
| + } | ||
| + | ||
| + private URL getConfigurationSource() { | ||
| + return getClass().getResource( getConfigurationName() ); | ||
| + } | ||
| + | ||
| + private String getConfigurationName() { | ||
| + return "settings.properties"; | ||
| + } | ||
| + | ||
| + protected void initOptions() { | ||
| + Options.load( getOptions() ); | ||
| + } | ||
| + | ||
| + private void initWindow() { | ||
| + setWindow( new MainWindow( this ) ); | ||
| + } | ||
| + | ||
| + private void initState( Stage stage ) { | ||
| + stageState = new StageState( stage, getState() ); | ||
| + } | ||
| + | ||
| + private void initStage( Stage stage ) { | ||
| + stage.getIcons().addAll( | ||
| + new Image( "org/markdownwriterfx/markdownwriterfx16.png" ), | ||
| + new Image( "org/markdownwriterfx/markdownwriterfx32.png" ), | ||
| + new Image( "org/markdownwriterfx/markdownwriterfx128.png" ), | ||
| + new Image( "org/markdownwriterfx/markdownwriterfx256.png" ), | ||
| + new Image( "org/markdownwriterfx/markdownwriterfx512.png" ) ); | ||
| + stage.setTitle( getApplicationTitle() ); | ||
| + stage.setScene( getScene() ); | ||
| + } | ||
| + | ||
| + private Scene getScene() { | ||
| + return getMainWindow().getScene(); | ||
| + } | ||
| + | ||
| + protected MainWindow getMainWindow() { | ||
| + return this.mainWindow; | ||
| + } | ||
| + | ||
| + private void setWindow( MainWindow mainWindow ) { | ||
| + this.mainWindow = mainWindow; | ||
| + } | ||
| + | ||
| + private StageState getStageState() { | ||
| + return this.stageState; | ||
| + } | ||
| + | ||
| + private void setStageState( StageState stageState ) { | ||
| + this.stageState = stageState; | ||
| + } | ||
| + | ||
| + private static Application getApplication() { | ||
| + return app; | ||
| + } | ||
| + | ||
| + protected void setConfiguration( PropertiesConfiguration configuration ) { | ||
| + this.configuration = configuration; | ||
| + } | ||
| + | ||
| + public static void showDocument( String uri ) { | ||
| + getApplication().getHostServices().showDocument( uri ); | ||
| + } | ||
| + | ||
| + static private Preferences getRootPreferences() { | ||
| + return userRoot().node( "markdownwriterfx" ); | ||
| + } | ||
| + | ||
| + private static Preferences getOptions() { | ||
| + return getRootPreferences().node( "options" ); | ||
| + } | ||
| + | ||
| + public static Preferences getState() { | ||
| + return getRootPreferences().node( "state" ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Unused. | ||
| + * | ||
| + * @return | ||
| + * | ||
| + * @throws URISyntaxException | ||
| + */ | ||
| + private Path getConfigurationPath() throws URISyntaxException { | ||
| + final Path appDir = getApplicationDirectory(); | ||
| + return get( appDir.toString(), getConfigurationName() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Unused. | ||
| + * | ||
| + * @return | ||
| + * | ||
| + * @throws URISyntaxException | ||
| + */ | ||
| + private Path getApplicationDirectory() throws URISyntaxException { | ||
| + final Path appPath = get( getApplicationPath() ); | ||
| + return appPath.getParent(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Unused. Returns the path to the application's start-up directory. | ||
| + * | ||
| + * @return A Path where the main class is running. | ||
| + */ | ||
| + private String getApplicationPath() throws URISyntaxException { | ||
| + return getClass().getProtectionDomain().getCodeSource().getLocation().toURI().getPath(); | ||
| + } | ||
| + | ||
| } | ||