| Author | Dave Jarvis <email> |
|---|---|
| Date | 2020-05-15 18:41:13 GMT-0700 |
| Commit | c63db64eb5d5448d97bdba4b7a8f121fca809eda |
| Parent | 3aefdc0 |
| <project version="4"> | ||
| <component name="CompilerConfiguration"> | ||
| - <bytecodeTargetLevel target="1.8" /> | ||
| + <bytecodeTargetLevel target="11" /> | ||
| </component> | ||
| </project> |
| <project version="4"> | ||
| <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||
| - <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="JDK1.8" project-jdk-type="JavaSDK"> | ||
| + <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="JDK1.8" project-jdk-type="JavaSDK"> | ||
| <output url="file://$PROJECT_DIR$/out" /> | ||
| </component> |
| - | ||
| +<?xml version="1.0" encoding="UTF-8"?> | ||
| +<project version="4"> | ||
| + <component name="Palette2"> | ||
| + <group name="Swing"> | ||
| + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> | ||
| + </item> | ||
| + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> | ||
| + </item> | ||
| + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> | ||
| + </item> | ||
| + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> | ||
| + </item> | ||
| + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> | ||
| + <initial-values> | ||
| + <property name="text" value="Button" /> | ||
| + </initial-values> | ||
| + </item> | ||
| + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | ||
| + <initial-values> | ||
| + <property name="text" value="RadioButton" /> | ||
| + </initial-values> | ||
| + </item> | ||
| + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | ||
| + <initial-values> | ||
| + <property name="text" value="CheckBox" /> | ||
| + </initial-values> | ||
| + </item> | ||
| + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> | ||
| + <initial-values> | ||
| + <property name="text" value="Label" /> | ||
| + </initial-values> | ||
| + </item> | ||
| + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | ||
| + <preferred-size width="150" height="-1" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | ||
| + <preferred-size width="150" height="-1" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | ||
| + <preferred-size width="150" height="-1" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||
| + <preferred-size width="150" height="50" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||
| + <preferred-size width="150" height="50" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||
| + <preferred-size width="150" height="50" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> | ||
| + </item> | ||
| + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||
| + <preferred-size width="150" height="50" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> | ||
| + <preferred-size width="150" height="50" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | ||
| + <preferred-size width="150" height="50" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | ||
| + <preferred-size width="200" height="200" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | ||
| + <preferred-size width="200" height="200" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | ||
| + </item> | ||
| + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | ||
| + </item> | ||
| + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> | ||
| + </item> | ||
| + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> | ||
| + </item> | ||
| + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> | ||
| + <preferred-size width="-1" height="20" /> | ||
| + </default-constraints> | ||
| + </item> | ||
| + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> | ||
| + </item> | ||
| + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> | ||
| + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> | ||
| + </item> | ||
| + </group> | ||
| + </component> | ||
| +</project> |
| <component name="ChangeListManager"> | ||
| <list default="true" id="3dcf7c8f-87b5-4d25-a804-39da40a621b8" name="Default Changelist" comment=""> | ||
| - <change beforePath="$PROJECT_DIR$/.idea/jarRepositories.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/jarRepositories.xml" afterDir="false" /> | ||
| + <change afterPath="$PROJECT_DIR$/.idea/uiDesigner.xml" afterDir="false" /> | ||
| + <change afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/MainFx.java" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/.idea/compiler.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/compiler.xml" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" /> | ||
| <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> | ||
| <change beforePath="$PROJECT_DIR$/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/build.gradle" afterDir="false" /> | ||
| - <change beforePath="$PROJECT_DIR$/libs/renjin-script-engine-0.9.2726-jar-with-dependencies.jar" beforeDir="false" /> | ||
| - <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/MainWindow.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/MainWindow.java" afterDir="false" /> | ||
| - <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/editors/EditorPane.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/editors/EditorPane.java" afterDir="false" /> | ||
| - <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/editors/markdown/LinkVisitor.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/editors/markdown/LinkVisitor.java" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/FileEditorTab.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/FileEditorTab.java" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/Main.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/Main.java" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/controls/WebHyperlink.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/controls/WebHyperlink.java" afterDir="false" /> | ||
| <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/editors/markdown/MarkdownEditorPane.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/editors/markdown/MarkdownEditorPane.java" afterDir="false" /> | ||
| - <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/processors/MarkdownProcessor.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/processors/MarkdownProcessor.java" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/processors/MarkdownCaretInsertionProcessor.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/processors/MarkdownCaretInsertionProcessor.java" afterDir="false" /> | ||
| + <change beforePath="$PROJECT_DIR$/src/main/java/com/scrivenvar/processors/XMLProcessor.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/java/com/scrivenvar/processors/XMLProcessor.java" afterDir="false" /> | ||
| </list> | ||
| <option name="SHOW_DIALOG" value="false" /> | ||
| <activation /> | ||
| </task> | ||
| - <projects_view /> | ||
| + <projects_view> | ||
| + <tree_state> | ||
| + <expand> | ||
| + <path> | ||
| + <item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" /> | ||
| + <item name="scrivenvar" type="f1a62948:ProjectNode" /> | ||
| + </path> | ||
| + </expand> | ||
| + <select /> | ||
| + </tree_state> | ||
| + </projects_view> | ||
| </state> | ||
| </system> | ||
| + </component> | ||
| + <component name="FileTemplateManagerImpl"> | ||
| + <option name="RECENT_TEMPLATES"> | ||
| + <list> | ||
| + <option value="Class" /> | ||
| + </list> | ||
| + </option> | ||
| </component> | ||
| <component name="Git.Settings"> | ||
| <property name="com.android.tools.idea.instantapp.provision.ProvisionBeforeRunTaskProvider.myTimeStamp" value="1541653415064" /> | ||
| <property name="last_opened_file_path" value="$PROJECT_DIR$" /> | ||
| - <property name="settings.editor.selected.configurable" value="preferences.sourceCode.Java" /> | ||
| + <property name="settings.editor.selected.configurable" value="reference.settingsdialog.project.gradle" /> | ||
| </component> | ||
| <component name="RunManager"> | ||
| </component> | ||
| <component name="WindowStateProjectService"> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.bottom" timestamp="1589575627707"> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.bottom" timestamp="1589593207664"> | ||
| <screen x="0" y="28" width="2560" height="1529" /> | ||
| </state> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.bottom/0.28.2560.1529@0.28.2560.1529" timestamp="1589575627707" /> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.center" timestamp="1589575627707"> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.bottom/0.28.2560.1529@0.28.2560.1529" timestamp="1589593207664" /> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.center" timestamp="1589593207663"> | ||
| <screen x="0" y="28" width="2560" height="1529" /> | ||
| </state> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.center/0.28.2560.1529@0.28.2560.1529" timestamp="1589575627707" /> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.left" timestamp="1589575627706"> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.center/0.28.2560.1529@0.28.2560.1529" timestamp="1589593207663" /> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.left" timestamp="1589593207663"> | ||
| <screen x="0" y="28" width="2560" height="1529" /> | ||
| </state> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.left/0.28.2560.1529@0.28.2560.1529" timestamp="1589575627706" /> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.right" timestamp="1589575627707"> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.left/0.28.2560.1529@0.28.2560.1529" timestamp="1589593207663" /> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.right" timestamp="1589593207664"> | ||
| <screen x="0" y="28" width="2560" height="1529" /> | ||
| </state> | ||
| - <state width="1573" height="321" key="GridCell.Tab.0.right/0.28.2560.1529@0.28.2560.1529" timestamp="1589575627707" /> | ||
| - <state x="490" y="327" width="672" height="678" key="search.everywhere.popup" timestamp="1589573707618"> | ||
| + <state width="1573" height="321" key="GridCell.Tab.0.right/0.28.2560.1529@0.28.2560.1529" timestamp="1589593207664" /> | ||
| + <state x="324" y="288" key="SettingsEditor" timestamp="1589576619807"> | ||
| <screen x="0" y="28" width="2560" height="1529" /> | ||
| </state> | ||
| - <state x="490" y="327" width="672" height="678" key="search.everywhere.popup/0.28.2560.1529@0.28.2560.1529" timestamp="1589573707618" /> | ||
| + <state x="324" y="288" key="SettingsEditor/0.28.2560.1529@0.28.2560.1529" timestamp="1589576619807" /> | ||
| + <state x="1071" y="397" width="1417" height="979" key="com.intellij.history.integration.ui.views.FileHistoryDialog" timestamp="1589582662731"> | ||
| + <screen x="0" y="28" width="2560" height="1529" /> | ||
| + </state> | ||
| + <state x="1071" y="397" width="1417" height="979" key="com.intellij.history.integration.ui.views.FileHistoryDialog/0.28.2560.1529@0.28.2560.1529" timestamp="1589582662731" /> | ||
| + <state x="490" y="304" key="run.anything.popup" timestamp="1589592158683"> | ||
| + <screen x="0" y="28" width="2560" height="1529" /> | ||
| + </state> | ||
| + <state x="490" y="304" key="run.anything.popup/0.28.2560.1529@0.28.2560.1529" timestamp="1589592158683" /> | ||
| + <state x="490" y="327" width="672" height="678" key="search.everywhere.popup" timestamp="1589586194905"> | ||
| + <screen x="0" y="28" width="2560" height="1529" /> | ||
| + </state> | ||
| + <state x="490" y="327" width="672" height="678" key="search.everywhere.popup/0.28.2560.1529@0.28.2560.1529" timestamp="1589586194905" /> | ||
| </component> | ||
| <component name="masterDetails"> | ||
| Build the application as follows: | ||
| - gradle build | ||
| + gradle jar | ||
| The application is built. |
| plugins { | ||
| id 'application' | ||
| - id 'java' | ||
| - id 'java-library-distribution' | ||
| id 'org.openjfx.javafxplugin' version '0.0.8' | ||
| -} | ||
| - | ||
| -javafx { | ||
| - version = "14" | ||
| - modules = [ 'javafx.controls', 'javafx.web' ] | ||
| } | ||
| url "https://nexus.bedatadriven.com/content/groups/public" | ||
| } | ||
| -} | ||
| - | ||
| -compileJava { | ||
| - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" | ||
| } | ||
| implementation 'com.vladsch.flexmark:flexmark-ext-superscript:0.61.28' | ||
| implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.61.28' | ||
| - compile 'com.fasterxml.jackson.core:jackson-core:2.9.3' | ||
| - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.3' | ||
| - compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.3' | ||
| - compile 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.9.3' | ||
| - compile 'org.ahocorasick:ahocorasick:0.4.0' | ||
| - compile 'org.yaml:snakeyaml:1.19' | ||
| - compile 'com.ximpleware:vtd-xml:2.13.4' | ||
| - compile 'net.sf.saxon:Saxon-HE:9.8.0-7' | ||
| + implementation 'com.fasterxml.jackson.core:jackson-core:2.11.0' | ||
| + implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.0' | ||
| + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.0' | ||
| + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.0' | ||
| + implementation 'org.ahocorasick:ahocorasick:0.4.0' | ||
| + implementation 'org.yaml:snakeyaml:1.26' | ||
| + implementation 'com.ximpleware:vtd-xml:2.13.4' | ||
| + implementation 'net.sf.saxon:Saxon-HE:10.1' | ||
| implementation 'org.apache.commons:commons-configuration2:2.7' | ||
| implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' | ||
| implementation 'de.jensd:fontawesomefx-commons:11.0' | ||
| implementation 'de.jensd:fontawesomefx-fontawesome:4.7.0-11' | ||
| implementation "org.renjin:renjin-script-engine:0.9.2726" | ||
| + | ||
| + def os = ['win', 'linux', 'mac'] | ||
| + def fx = ['controls', 'graphics', 'web', 'fxml'] | ||
| + | ||
| + fx.each { fxitem -> | ||
| + os.each { ositem -> | ||
| + runtimeOnly "org.openjfx:javafx-${fxitem}:${javafx.version}:${ositem}" | ||
| + } | ||
| + } | ||
| } | ||
| -version = '1.3.8' | ||
| +javafx { | ||
| + version = "14" | ||
| + modules = ['javafx.controls', 'javafx.graphics', 'javafx.web'] | ||
| +} | ||
| + | ||
| +compileJava { | ||
| + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" | ||
| +} | ||
| + | ||
| +version = '1.4.0' | ||
| applicationName = 'scrivenvar' | ||
| -mainClassName = 'com.scrivenvar.Main' | ||
| -sourceCompatibility = JavaVersion.VERSION_1_8 | ||
| +mainClassName = 'com.scrivenvar.MainFx' | ||
| +def launcherClassName = 'com.scrivenvar.Main' | ||
| +sourceCompatibility = JavaVersion.VERSION_11 | ||
| jar { | ||
| - baseName = applicationName | ||
| - archiveName = "${applicationName}.jar" | ||
| - | ||
| - doFirst { | ||
| - from { | ||
| - configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } | ||
| + manifest { | ||
| + attributes 'Main-Class': launcherClassName | ||
| + } | ||
| + | ||
| + from { | ||
| + (configurations.runtimeClasspath.findAll { !it.path.endsWith(".pom") }).collect { | ||
| + it.isDirectory() ? it : zipTree(it) | ||
| } | ||
| } | ||
| - // Remove digital signature files to ensure an executable JAR file. | ||
| - exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' | ||
| + archiveFileName = 'scrivenvar.jar' | ||
| - manifest { | ||
| - attributes 'Main-Class': mainClassName | ||
| - attributes 'Class-Path': configurations.compile.collect { | ||
| - 'libs/' + it.getName() | ||
| - }.join(' ') | ||
| - } | ||
| + exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' | ||
| } | ||
| distributions { | ||
| main { | ||
| - baseName = applicationName | ||
| + distributionBaseName = applicationName | ||
| contents { | ||
| from { ['LICENSE.md', 'README.md'] } | ||
| - into( 'images' ) { | ||
| + into('images') { | ||
| from { 'images' } | ||
| } | ||
| import com.scrivenvar.service.events.Notification; | ||
| import com.scrivenvar.service.events.Notifier; | ||
| -import java.io.IOException; | ||
| -import java.nio.charset.Charset; | ||
| -import static java.nio.charset.StandardCharsets.UTF_8; | ||
| -import java.nio.file.Files; | ||
| -import java.nio.file.Path; | ||
| -import static java.util.Locale.ENGLISH; | ||
| -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.beans.value.ChangeListener; | ||
| -import javafx.beans.value.ObservableValue; | ||
| -import javafx.event.Event; | ||
| -import javafx.scene.Node; | ||
| -import javafx.scene.Scene; | ||
| -import javafx.scene.control.Tab; | ||
| -import javafx.scene.control.Tooltip; | ||
| -import javafx.scene.input.InputEvent; | ||
| -import javafx.scene.text.Text; | ||
| -import javafx.stage.Window; | ||
| -import org.fxmisc.richtext.StyleClassedTextArea; | ||
| -import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward; | ||
| -import org.fxmisc.richtext.model.TwoDimensional.Position; | ||
| -import org.fxmisc.undo.UndoManager; | ||
| -import org.fxmisc.wellbehaved.event.EventPattern; | ||
| -import org.fxmisc.wellbehaved.event.InputMap; | ||
| -import org.mozilla.universalchardet.UniversalDetector; | ||
| - | ||
| -/** | ||
| - * Editor for a single file. | ||
| - * | ||
| - * @author Karl Tauber and White Magic Software, Ltd. | ||
| - */ | ||
| -public final class FileEditorTab extends Tab { | ||
| - | ||
| - /** | ||
| - * | ||
| - */ | ||
| - private final Notifier alertService = Services.load( Notifier.class ); | ||
| - private EditorPane editorPane; | ||
| - | ||
| - /** | ||
| - * Character encoding used by the file (or default encoding if none found). | ||
| - */ | ||
| - private Charset encoding; | ||
| - | ||
| - private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper(); | ||
| - private final BooleanProperty canUndo = new SimpleBooleanProperty(); | ||
| - private final BooleanProperty canRedo = new SimpleBooleanProperty(); | ||
| - | ||
| - private Path path; | ||
| - | ||
| - public FileEditorTab( final Path path ) { | ||
| - setPath( path ); | ||
| - | ||
| - this.modified.addListener( (observable, oldPath, newPath) -> updateTab() ); | ||
| - | ||
| - setOnSelectionChanged( e -> { | ||
| - if( isSelected() ) { | ||
| - Platform.runLater( () -> activated() ); | ||
| - } | ||
| - } ); | ||
| - } | ||
| - | ||
| - private void updateTab() { | ||
| - setText( getTabTitle() ); | ||
| - setGraphic( getModifiedMark() ); | ||
| - setTooltip( getTabTooltip() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the base filename (without the directory names). | ||
| - * | ||
| - * @return The untitled text if the path hasn't been set. | ||
| - */ | ||
| - private String getTabTitle() { | ||
| - final Path filePath = getPath(); | ||
| - | ||
| - return (filePath == null) | ||
| - ? Messages.get( "FileEditor.untitled" ) | ||
| - : filePath.getFileName().toString(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the full filename represented by the path. | ||
| - * | ||
| - * @return The untitled text if the path hasn't been set. | ||
| - */ | ||
| - private Tooltip getTabTooltip() { | ||
| - final Path filePath = getPath(); | ||
| - return new Tooltip( filePath == null ? "" : filePath.toString() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns a marker to indicate whether the file has been modified. | ||
| - * | ||
| - * @return "*" when the file has changed; otherwise null. | ||
| - */ | ||
| - private Text getModifiedMark() { | ||
| - return isModified() ? new Text( "*" ) : null; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Called when the user switches tab. | ||
| - */ | ||
| - private void activated() { | ||
| - // Tab is closed or no longer active. | ||
| - if( getTabPane() == null || !isSelected() ) { | ||
| - return; | ||
| - } | ||
| - | ||
| - // Switch to the tab without loading if the contents are already in memory. | ||
| - if( getContent() != null ) { | ||
| - getEditorPane().requestFocus(); | ||
| - return; | ||
| - } | ||
| - | ||
| - // Load the text and update the preview before the undo manager. | ||
| - load(); | ||
| - | ||
| - // Track undo requests -- can only be called *after* load. | ||
| - initUndoManager(); | ||
| - initLayout(); | ||
| - initFocus(); | ||
| - } | ||
| - | ||
| - private void initLayout() { | ||
| - setContent( getScrollPane() ); | ||
| - } | ||
| - | ||
| - private Node getScrollPane() { | ||
| - return getEditorPane().getScrollPane(); | ||
| - } | ||
| - | ||
| - private void initFocus() { | ||
| - getEditorPane().requestFocus(); | ||
| - } | ||
| - | ||
| - private void initUndoManager() { | ||
| - final UndoManager undoManager = getUndoManager(); | ||
| - | ||
| - // Clear undo history after first load. | ||
| - undoManager.forgetHistory(); | ||
| - | ||
| - // Bind the editor undo manager to the properties. | ||
| - modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) ); | ||
| - canUndo.bind( undoManager.undoAvailableProperty() ); | ||
| - canRedo.bind( undoManager.redoAvailableProperty() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Searches from the caret position forward for the given string. | ||
| - * | ||
| - * @param needle The text string to match. | ||
| - */ | ||
| - public void searchNext( final String needle ) { | ||
| - final String haystack = getEditorText(); | ||
| - int index = haystack.indexOf( needle, getCaretPosition() ); | ||
| - | ||
| - // Wrap around. | ||
| - if( index == -1 ) { | ||
| - index = haystack.indexOf( needle, 0 ); | ||
| - } | ||
| - | ||
| - if( index >= 0 ) { | ||
| - setCaretPosition( index ); | ||
| - getEditor().selectRange( index, index + needle.length() ); | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the index into the text where the caret blinks happily away. | ||
| - * | ||
| - * @return A number from 0 to the editor's document text length. | ||
| - */ | ||
| - public int getCaretPosition() { | ||
| - return getEditor().getCaretPosition(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Moves the caret to a given offset. | ||
| - * | ||
| - * @param offset The new caret offset. | ||
| - */ | ||
| - private void setCaretPosition( final int offset ) { | ||
| - getEditor().moveTo( offset ); | ||
| - getEditor().requestFollowCaret(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the caret's current row and column position. | ||
| - * | ||
| - * @return The caret's offset into the document. | ||
| - */ | ||
| - public Position getCaretOffset() { | ||
| - return getEditor().offsetToPosition( getCaretPosition(), Forward ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Allows observers to synchronize caret position changes. | ||
| - * | ||
| - * @return An observable caret property value. | ||
| - */ | ||
| - public final ObservableValue<Integer> caretPositionProperty() { | ||
| - return getEditor().caretPositionProperty(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the text area associated with this tab. | ||
| - * | ||
| - * @return A text editor. | ||
| - */ | ||
| - private StyleClassedTextArea getEditor() { | ||
| - return getEditorPane().getEditor(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns true if the given path exactly matches this tab's path. | ||
| - * | ||
| - * @param check The path to compare against. | ||
| - * | ||
| - * @return true The paths are the same. | ||
| - */ | ||
| - public boolean isPath( final Path check ) { | ||
| - final Path filePath = getPath(); | ||
| - | ||
| - return filePath == null ? false : filePath.equals( check ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Reads the entire file contents from the path associated with this tab. | ||
| - */ | ||
| - private void load() { | ||
| - final Path filePath = getPath(); | ||
| - | ||
| - if( filePath != null ) { | ||
| - try { | ||
| - getEditorPane().setText( asString( Files.readAllBytes( filePath ) ) ); | ||
| - getEditorPane().scrollToTop(); | ||
| - } catch( final IOException ex ) { | ||
| - getNotifyService().notify( ex ); | ||
| - } | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Saves the entire file contents from the path associated with this tab. | ||
| - * | ||
| - * @return true The file has been saved. | ||
| - */ | ||
| - public boolean save() { | ||
| - try { | ||
| - final EditorPane editor = getEditorPane(); | ||
| - Files.write( getPath(), asBytes( editor.getText() ) ); | ||
| - editor.getUndoManager().mark(); | ||
| - return true; | ||
| - } catch( final IOException ex ) { | ||
| - return alert( | ||
| - "FileEditor.saveFailed.title", "FileEditor.saveFailed.message", ex | ||
| - ); | ||
| - } | ||
| - } | ||
| - | ||
| - /** | ||
| - * Creates an alert dialog and waits for it to close. | ||
| - * | ||
| - * @param titleKey Resource bundle key for the alert dialog title. | ||
| - * @param messageKey Resource bundle key for the alert dialog message. | ||
| - * @param e The unexpected happening. | ||
| - * | ||
| - * @return false | ||
| - */ | ||
| - private boolean alert( | ||
| - final String titleKey, final String messageKey, final Exception e ) { | ||
| - final Notifier service = getNotifyService(); | ||
| - final Path filePath = getPath(); | ||
| - | ||
| - final Notification message = service.createNotification( | ||
| - Messages.get( titleKey ), | ||
| - Messages.get( messageKey ), | ||
| - filePath == null ? "" : filePath, | ||
| - e.getMessage() | ||
| - ); | ||
| - | ||
| - try { | ||
| - service.createError( getWindow(), message ).showAndWait(); | ||
| - } catch( final Exception ex ) { | ||
| - getNotifyService().notify( ex ); | ||
| - } | ||
| - | ||
| - return false; | ||
| - } | ||
| - | ||
| - private Window getWindow() { | ||
| - final Scene scene = getEditorPane().getScene(); | ||
| - | ||
| - if( scene == null ) { | ||
| - throw new UnsupportedOperationException( "" ); | ||
| - } | ||
| - | ||
| - return scene.getWindow(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns a best guess at the file encoding. If the encoding could not be | ||
| - * detected, this will return the default charset for the JVM. | ||
| - * | ||
| - * @param bytes The bytes to perform character encoding detection. | ||
| - * | ||
| - * @return The character encoding. | ||
| - */ | ||
| - private Charset detectEncoding( final byte[] bytes ) { | ||
| - final UniversalDetector detector = new UniversalDetector( null ); | ||
| - detector.handleData( bytes, 0, bytes.length ); | ||
| - detector.dataEnd(); | ||
| - | ||
| - final String charset = detector.getDetectedCharset(); | ||
| - final Charset charEncoding = charset == null | ||
| - ? Charset.defaultCharset() | ||
| - : Charset.forName( charset.toUpperCase( ENGLISH ) ); | ||
| - | ||
| - detector.reset(); | ||
| - | ||
| - return charEncoding; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Converts the given string to an array of bytes using the encoding that was | ||
| - * originally detected (if any) and associated with this file. | ||
| - * | ||
| - * @param text The text to convert into the original file encoding. | ||
| - * | ||
| - * @return A series of bytes ready for writing to a file. | ||
| - */ | ||
| - private byte[] asBytes( final String text ) { | ||
| - return text.getBytes( getEncoding() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Converts the given bytes into a Java String. This will call setEncoding | ||
| - * with the encoding detected by the CharsetDetector. | ||
| - * | ||
| - * @param text The text of unknown character encoding. | ||
| - * | ||
| - * @return The text, in its auto-detected encoding, as a String. | ||
| - */ | ||
| - private String asString( final byte[] text ) { | ||
| - setEncoding( detectEncoding( text ) ); | ||
| - return new String( text, getEncoding() ); | ||
| - } | ||
| - | ||
| - public Path getPath() { | ||
| - return this.path; | ||
| - } | ||
| - | ||
| - public void setPath( final Path path ) { | ||
| - this.path = path; | ||
| - | ||
| - updateTab(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Answers whether this tab has an initialized path reference. | ||
| - * | ||
| - * @return false This tab has no path. | ||
| - */ | ||
| - public boolean isFileOpen() { | ||
| - return this.path != null; | ||
| - } | ||
| - | ||
| - public 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(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Forwards the request to the editor pane. | ||
| - * | ||
| - * @param <T> The type of event listener to add. | ||
| - * @param <U> The type of consumer to add. | ||
| - * @param event The event that should trigger updates to the listener. | ||
| - * @param consumer The listener to receive update events. | ||
| - */ | ||
| - public <T extends Event, U extends T> void addEventListener( | ||
| - final EventPattern<? super T, ? extends U> event, | ||
| - final Consumer<? super U> consumer ) { | ||
| - getEditorPane().addKeyboardListener( event, consumer ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Forwards to the editor pane's listeners for keyboard events. | ||
| - * | ||
| - * @param map The new input map to replace the existing keyboard listener. | ||
| - */ | ||
| - public void addEventListener( final InputMap<InputEvent> map ) { | ||
| - getEditorPane().addEventListener( map ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Forwards to the editor pane's listeners for keyboard events. | ||
| - * | ||
| - * @param map The existing input map to remove from the keyboard listeners. | ||
| - */ | ||
| - public void removeEventListener( final InputMap<InputEvent> map ) { | ||
| - getEditorPane().removeEventListener( map ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Forwards to the editor pane's listeners for text change events. | ||
| - * | ||
| - * @param listener The listener to notify when the text changes. | ||
| - */ | ||
| - public void addTextChangeListener( final ChangeListener<String> listener ) { | ||
| - getEditorPane().addTextChangeListener( listener ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Forwards to the editor pane's listeners for caret paragraph change events. | ||
| - * | ||
| - * @param listener The listener to notify when the caret changes paragraphs. | ||
| - */ | ||
| - public void addCaretParagraphListener( final ChangeListener<Integer> listener ) { | ||
| - getEditorPane().addCaretParagraphListener( listener ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Forwards the request to the editor pane. | ||
| - * | ||
| - * @return The text to process. | ||
| - */ | ||
| - public String getEditorText() { | ||
| - return getEditorPane().getText(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the editor pane, or creates one if it doesn't yet exist. | ||
| - * | ||
| - * @return The editor pane, never null. | ||
| - */ | ||
| - public synchronized EditorPane getEditorPane() { | ||
| - if( this.editorPane == null ) { | ||
| - this.editorPane = new MarkdownEditorPane(); | ||
| - } | ||
| - | ||
| - return this.editorPane; | ||
| - } | ||
| - | ||
| - private Notifier getNotifyService() { | ||
| - return this.alertService; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Returns the encoding for the file, defaulting to UTF-8 if it hasn't been | ||
| - * determined. | ||
| - * | ||
| - * @return The file encoding or UTF-8 if unknown. | ||
| - */ | ||
| - private Charset getEncoding() { | ||
| - if( this.encoding == null ) { | ||
| - this.encoding = UTF_8; | ||
| - } | ||
| - | ||
| +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.beans.value.ChangeListener; | ||
| +import javafx.beans.value.ObservableValue; | ||
| +import javafx.event.Event; | ||
| +import javafx.scene.Node; | ||
| +import javafx.scene.Scene; | ||
| +import javafx.scene.control.Tab; | ||
| +import javafx.scene.control.Tooltip; | ||
| +import javafx.scene.input.InputEvent; | ||
| +import javafx.scene.text.Text; | ||
| +import javafx.stage.Window; | ||
| +import org.fxmisc.richtext.StyleClassedTextArea; | ||
| +import org.fxmisc.richtext.model.TwoDimensional.Position; | ||
| +import org.fxmisc.undo.UndoManager; | ||
| +import org.fxmisc.wellbehaved.event.EventPattern; | ||
| +import org.fxmisc.wellbehaved.event.InputMap; | ||
| +import org.mozilla.universalchardet.UniversalDetector; | ||
| + | ||
| +import java.io.IOException; | ||
| +import java.nio.charset.Charset; | ||
| +import java.nio.file.Files; | ||
| +import java.nio.file.Path; | ||
| +import java.util.function.Consumer; | ||
| + | ||
| +import static java.nio.charset.StandardCharsets.UTF_8; | ||
| +import static java.util.Locale.ENGLISH; | ||
| +import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward; | ||
| + | ||
| +/** | ||
| + * Editor for a single file. | ||
| + * | ||
| + * @author Karl Tauber and White Magic Software, Ltd. | ||
| + */ | ||
| +public final class FileEditorTab extends Tab { | ||
| + | ||
| + /** | ||
| + * | ||
| + */ | ||
| + private final Notifier alertService = Services.load( Notifier.class ); | ||
| + private EditorPane editorPane; | ||
| + | ||
| + /** | ||
| + * Character encoding used by the file (or default encoding if none found). | ||
| + */ | ||
| + private Charset encoding; | ||
| + | ||
| + private final ReadOnlyBooleanWrapper modified = new ReadOnlyBooleanWrapper(); | ||
| + private final BooleanProperty canUndo = new SimpleBooleanProperty(); | ||
| + private final BooleanProperty canRedo = new SimpleBooleanProperty(); | ||
| + | ||
| + private Path path; | ||
| + | ||
| + public FileEditorTab( final Path path ) { | ||
| + setPath( path ); | ||
| + | ||
| + this.modified.addListener( ( observable, oldPath, newPath ) -> updateTab() ); | ||
| + | ||
| + setOnSelectionChanged( e -> { | ||
| + if( isSelected() ) { | ||
| + Platform.runLater( this::activated ); | ||
| + } | ||
| + } ); | ||
| + } | ||
| + | ||
| + private void updateTab() { | ||
| + setText( getTabTitle() ); | ||
| + setGraphic( getModifiedMark() ); | ||
| + setTooltip( getTabTooltip() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the base filename (without the directory names). | ||
| + * | ||
| + * @return The untitled text if the path hasn't been set. | ||
| + */ | ||
| + private String getTabTitle() { | ||
| + final Path filePath = getPath(); | ||
| + | ||
| + return (filePath == null) | ||
| + ? Messages.get( "FileEditor.untitled" ) | ||
| + : filePath.getFileName().toString(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the full filename represented by the path. | ||
| + * | ||
| + * @return The untitled text if the path hasn't been set. | ||
| + */ | ||
| + private Tooltip getTabTooltip() { | ||
| + final Path filePath = getPath(); | ||
| + return new Tooltip( filePath == null ? "" : filePath.toString() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a marker to indicate whether the file has been modified. | ||
| + * | ||
| + * @return "*" when the file has changed; otherwise null. | ||
| + */ | ||
| + private Text getModifiedMark() { | ||
| + return isModified() ? new Text( "*" ) : null; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Called when the user switches tab. | ||
| + */ | ||
| + private void activated() { | ||
| + // Tab is closed or no longer active. | ||
| + if( getTabPane() == null || !isSelected() ) { | ||
| + return; | ||
| + } | ||
| + | ||
| + // Switch to the tab without loading if the contents are already in memory. | ||
| + if( getContent() != null ) { | ||
| + getEditorPane().requestFocus(); | ||
| + return; | ||
| + } | ||
| + | ||
| + // Load the text and update the preview before the undo manager. | ||
| + load(); | ||
| + | ||
| + // Track undo requests -- can only be called *after* load. | ||
| + initUndoManager(); | ||
| + initLayout(); | ||
| + initFocus(); | ||
| + } | ||
| + | ||
| + private void initLayout() { | ||
| + setContent( getScrollPane() ); | ||
| + } | ||
| + | ||
| + private Node getScrollPane() { | ||
| + return getEditorPane().getScrollPane(); | ||
| + } | ||
| + | ||
| + private void initFocus() { | ||
| + getEditorPane().requestFocus(); | ||
| + } | ||
| + | ||
| + private void initUndoManager() { | ||
| + final UndoManager undoManager = getUndoManager(); | ||
| + | ||
| + // Clear undo history after first load. | ||
| + undoManager.forgetHistory(); | ||
| + | ||
| + // Bind the editor undo manager to the properties. | ||
| + modified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) ); | ||
| + canUndo.bind( undoManager.undoAvailableProperty() ); | ||
| + canRedo.bind( undoManager.redoAvailableProperty() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Searches from the caret position forward for the given string. | ||
| + * | ||
| + * @param needle The text string to match. | ||
| + */ | ||
| + public void searchNext( final String needle ) { | ||
| + final String haystack = getEditorText(); | ||
| + int index = haystack.indexOf( needle, getCaretPosition() ); | ||
| + | ||
| + // Wrap around. | ||
| + if( index == -1 ) { | ||
| + index = haystack.indexOf( needle, 0 ); | ||
| + } | ||
| + | ||
| + if( index >= 0 ) { | ||
| + setCaretPosition( index ); | ||
| + getEditor().selectRange( index, index + needle.length() ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the index into the text where the caret blinks happily away. | ||
| + * | ||
| + * @return A number from 0 to the editor's document text length. | ||
| + */ | ||
| + public int getCaretPosition() { | ||
| + return getEditor().getCaretPosition(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Moves the caret to a given offset. | ||
| + * | ||
| + * @param offset The new caret offset. | ||
| + */ | ||
| + private void setCaretPosition( final int offset ) { | ||
| + getEditor().moveTo( offset ); | ||
| + getEditor().requestFollowCaret(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the caret's current row and column position. | ||
| + * | ||
| + * @return The caret's offset into the document. | ||
| + */ | ||
| + public Position getCaretOffset() { | ||
| + return getEditor().offsetToPosition( getCaretPosition(), Forward ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Allows observers to synchronize caret position changes. | ||
| + * | ||
| + * @return An observable caret property value. | ||
| + */ | ||
| + public final ObservableValue<Integer> caretPositionProperty() { | ||
| + return getEditor().caretPositionProperty(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the text area associated with this tab. | ||
| + * | ||
| + * @return A text editor. | ||
| + */ | ||
| + private StyleClassedTextArea getEditor() { | ||
| + return getEditorPane().getEditor(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns true if the given path exactly matches this tab's path. | ||
| + * | ||
| + * @param check The path to compare against. | ||
| + * @return true The paths are the same. | ||
| + */ | ||
| + public boolean isPath( final Path check ) { | ||
| + final Path filePath = getPath(); | ||
| + | ||
| + return filePath != null && filePath.equals( check ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Reads the entire file contents from the path associated with this tab. | ||
| + */ | ||
| + private void load() { | ||
| + final Path filePath = getPath(); | ||
| + | ||
| + if( filePath != null ) { | ||
| + try { | ||
| + getEditorPane().setText( asString( Files.readAllBytes( filePath ) ) ); | ||
| + getEditorPane().scrollToTop(); | ||
| + } catch( final IOException ex ) { | ||
| + getNotifyService().notify( ex ); | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Saves the entire file contents from the path associated with this tab. | ||
| + * | ||
| + * @return true The file has been saved. | ||
| + */ | ||
| + public boolean save() { | ||
| + try { | ||
| + final EditorPane editor = getEditorPane(); | ||
| + Files.write( getPath(), asBytes( editor.getText() ) ); | ||
| + editor.getUndoManager().mark(); | ||
| + return true; | ||
| + } catch( final IOException ex ) { | ||
| + return alert( | ||
| + "FileEditor.saveFailed.title", "FileEditor.saveFailed.message", ex | ||
| + ); | ||
| + } | ||
| + } | ||
| + | ||
| + /** | ||
| + * Creates an alert dialog and waits for it to close. | ||
| + * | ||
| + * @param titleKey Resource bundle key for the alert dialog title. | ||
| + * @param messageKey Resource bundle key for the alert dialog message. | ||
| + * @param e The unexpected happening. | ||
| + * @return false | ||
| + */ | ||
| + private boolean alert( | ||
| + final String titleKey, final String messageKey, final Exception e ) { | ||
| + final Notifier service = getNotifyService(); | ||
| + final Path filePath = getPath(); | ||
| + | ||
| + final Notification message = service.createNotification( | ||
| + Messages.get( titleKey ), | ||
| + Messages.get( messageKey ), | ||
| + filePath == null ? "" : filePath, | ||
| + e.getMessage() | ||
| + ); | ||
| + | ||
| + try { | ||
| + service.createError( getWindow(), message ).showAndWait(); | ||
| + } catch( final Exception ex ) { | ||
| + getNotifyService().notify( ex ); | ||
| + } | ||
| + | ||
| + return false; | ||
| + } | ||
| + | ||
| + private Window getWindow() { | ||
| + final Scene scene = getEditorPane().getScene(); | ||
| + | ||
| + if( scene == null ) { | ||
| + throw new UnsupportedOperationException( "" ); | ||
| + } | ||
| + | ||
| + return scene.getWindow(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a best guess at the file encoding. If the encoding could not be | ||
| + * detected, this will return the default charset for the JVM. | ||
| + * | ||
| + * @param bytes The bytes to perform character encoding detection. | ||
| + * @return The character encoding. | ||
| + */ | ||
| + private Charset detectEncoding( final byte[] bytes ) { | ||
| + final UniversalDetector detector = new UniversalDetector( null ); | ||
| + detector.handleData( bytes, 0, bytes.length ); | ||
| + detector.dataEnd(); | ||
| + | ||
| + final String charset = detector.getDetectedCharset(); | ||
| + final Charset charEncoding = charset == null | ||
| + ? Charset.defaultCharset() | ||
| + : Charset.forName( charset.toUpperCase( ENGLISH ) ); | ||
| + | ||
| + detector.reset(); | ||
| + | ||
| + return charEncoding; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Converts the given string to an array of bytes using the encoding that was | ||
| + * originally detected (if any) and associated with this file. | ||
| + * | ||
| + * @param text The text to convert into the original file encoding. | ||
| + * @return A series of bytes ready for writing to a file. | ||
| + */ | ||
| + private byte[] asBytes( final String text ) { | ||
| + return text.getBytes( getEncoding() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Converts the given bytes into a Java String. This will call setEncoding | ||
| + * with the encoding detected by the CharsetDetector. | ||
| + * | ||
| + * @param text The text of unknown character encoding. | ||
| + * @return The text, in its auto-detected encoding, as a String. | ||
| + */ | ||
| + private String asString( final byte[] text ) { | ||
| + setEncoding( detectEncoding( text ) ); | ||
| + return new String( text, getEncoding() ); | ||
| + } | ||
| + | ||
| + public Path getPath() { | ||
| + return this.path; | ||
| + } | ||
| + | ||
| + public void setPath( final Path path ) { | ||
| + this.path = path; | ||
| + | ||
| + updateTab(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether this tab has an initialized path reference. | ||
| + * | ||
| + * @return false This tab has no path. | ||
| + */ | ||
| + public boolean isFileOpen() { | ||
| + return this.path != null; | ||
| + } | ||
| + | ||
| + public 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(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Forwards the request to the editor pane. | ||
| + * | ||
| + * @param <T> The type of event listener to add. | ||
| + * @param <U> The type of consumer to add. | ||
| + * @param event The event that should trigger updates to the listener. | ||
| + * @param consumer The listener to receive update events. | ||
| + */ | ||
| + public <T extends Event, U extends T> void addEventListener( | ||
| + final EventPattern<? super T, ? extends U> event, | ||
| + final Consumer<? super U> consumer ) { | ||
| + getEditorPane().addKeyboardListener( event, consumer ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Forwards to the editor pane's listeners for keyboard events. | ||
| + * | ||
| + * @param map The new input map to replace the existing keyboard listener. | ||
| + */ | ||
| + public void addEventListener( final InputMap<InputEvent> map ) { | ||
| + getEditorPane().addEventListener( map ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Forwards to the editor pane's listeners for keyboard events. | ||
| + * | ||
| + * @param map The existing input map to remove from the keyboard listeners. | ||
| + */ | ||
| + public void removeEventListener( final InputMap<InputEvent> map ) { | ||
| + getEditorPane().removeEventListener( map ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Forwards to the editor pane's listeners for text change events. | ||
| + * | ||
| + * @param listener The listener to notify when the text changes. | ||
| + */ | ||
| + public void addTextChangeListener( final ChangeListener<String> listener ) { | ||
| + getEditorPane().addTextChangeListener( listener ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Forwards to the editor pane's listeners for caret paragraph change events. | ||
| + * | ||
| + * @param listener The listener to notify when the caret changes paragraphs. | ||
| + */ | ||
| + public void addCaretParagraphListener( | ||
| + final ChangeListener<Integer> listener ) { | ||
| + getEditorPane().addCaretParagraphListener( listener ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Forwards the request to the editor pane. | ||
| + * | ||
| + * @return The text to process. | ||
| + */ | ||
| + public String getEditorText() { | ||
| + return getEditorPane().getText(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the editor pane, or creates one if it doesn't yet exist. | ||
| + * | ||
| + * @return The editor pane, never null. | ||
| + */ | ||
| + public synchronized EditorPane getEditorPane() { | ||
| + if( this.editorPane == null ) { | ||
| + this.editorPane = new MarkdownEditorPane(); | ||
| + } | ||
| + | ||
| + return this.editorPane; | ||
| + } | ||
| + | ||
| + private Notifier getNotifyService() { | ||
| + return this.alertService; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns the encoding for the file, defaulting to UTF-8 if it hasn't been | ||
| + * determined. | ||
| + * | ||
| + * @return The file encoding or UTF-8 if unknown. | ||
| + */ | ||
| + private Charset getEncoding() { | ||
| + if( this.encoding == null ) { | ||
| + this.encoding = UTF_8; | ||
| + } | ||
| + | ||
| return this.encoding; | ||
| } |
| /* | ||
| - * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | ||
| + * Copyright 2020 White Magic Software, Ltd. | ||
| * | ||
| * All rights reserved. | ||
| */ | ||
| package com.scrivenvar; | ||
| - | ||
| -import static com.scrivenvar.Constants.*; | ||
| -import static com.scrivenvar.Messages.get; | ||
| -import com.scrivenvar.preferences.FilePreferencesFactory; | ||
| -import com.scrivenvar.service.Options; | ||
| -import com.scrivenvar.service.Snitch; | ||
| -import com.scrivenvar.service.events.Notifier; | ||
| -import com.scrivenvar.util.StageState; | ||
| -import java.util.logging.LogManager; | ||
| -import javafx.application.Application; | ||
| -import javafx.scene.Scene; | ||
| -import javafx.scene.image.Image; | ||
| -import javafx.stage.Stage; | ||
| /** | ||
| - * Main application entry point. The application allows users to edit Markdown | ||
| - * files and see a real-time preview of the edits. | ||
| - * | ||
| - * @author Karl Tauber and White Magic Software, Ltd. | ||
| + * Delegates to launching the application using the {@link MainFx} class. | ||
| */ | ||
| -public final class Main extends Application { | ||
| - | ||
| - private Options options; | ||
| - private Snitch snitch; | ||
| - private Thread snitchThread; | ||
| - | ||
| - private static Application app; | ||
| - private final MainWindow mainWindow = new MainWindow(); | ||
| - | ||
| +public class Main { | ||
| public static void main( final String[] args ) { | ||
| - initLogger(); | ||
| - initPreferences(); | ||
| - launch( args ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Prevents JavaFX from logging to standard error. | ||
| - * | ||
| - * @see http://stackoverflow.com/a/41476462/59087 | ||
| - */ | ||
| - private static void initLogger() { | ||
| - LogManager.getLogManager().reset(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Sets the factory used for reading user preferences. | ||
| - */ | ||
| - private static void initPreferences() { | ||
| - System.setProperty( | ||
| - "java.util.prefs.PreferencesFactory", | ||
| - FilePreferencesFactory.class.getName() | ||
| - ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Application entry point. | ||
| - * | ||
| - * @param stage The primary application stage. | ||
| - * | ||
| - * @throws Exception Could not read configuration file. | ||
| - */ | ||
| - @Override | ||
| - public void start( final Stage stage ) throws Exception { | ||
| - initApplication(); | ||
| - initNotifyService(); | ||
| - initState( stage ); | ||
| - initStage( stage ); | ||
| - initSnitch(); | ||
| - | ||
| - stage.show(); | ||
| - } | ||
| - | ||
| - public static void showDocument( final String uri ) { | ||
| - getApplication().getHostServices().showDocument( uri ); | ||
| - } | ||
| - | ||
| - private void initApplication() { | ||
| - Main.app = this; | ||
| - } | ||
| - | ||
| - /** | ||
| - * Constructs the notify service and appends the main window to the list of | ||
| - * notification observers. | ||
| - */ | ||
| - private void initNotifyService() { | ||
| - final Notifier notifier = Services.load( Notifier.class ); | ||
| - notifier.addObserver( getMainWindow() ); | ||
| - } | ||
| - | ||
| - private StageState initState( final Stage stage ) { | ||
| - return new StageState( stage, getOptions().getState() ); | ||
| - } | ||
| - | ||
| - private void initStage( final Stage stage ) { | ||
| - stage.getIcons().addAll( | ||
| - createImage( FILE_LOGO_16 ), | ||
| - createImage( FILE_LOGO_32 ), | ||
| - createImage( FILE_LOGO_128 ), | ||
| - createImage( FILE_LOGO_256 ), | ||
| - createImage( FILE_LOGO_512 ) ); | ||
| - stage.setTitle( getApplicationTitle() ); | ||
| - stage.setScene( getScene() ); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Watch for file system changes. | ||
| - */ | ||
| - private void initSnitch() { | ||
| - setSnitchThread( new Thread( getSnitch() ) ); | ||
| - getSnitchThread().start(); | ||
| - } | ||
| - | ||
| - /** | ||
| - * Stops the snitch service, if its running. | ||
| - * | ||
| - * @throws InterruptedException Couldn't stop the snitch thread. | ||
| - */ | ||
| - @Override | ||
| - public void stop() throws InterruptedException { | ||
| - getSnitch().stop(); | ||
| - | ||
| - final Thread thread = getSnitchThread(); | ||
| - | ||
| - if( thread != null ) { | ||
| - thread.interrupt(); | ||
| - thread.join(); | ||
| - } | ||
| - } | ||
| - | ||
| - private synchronized Snitch getSnitch() { | ||
| - if( this.snitch == null ) { | ||
| - this.snitch = Services.load( Snitch.class ); | ||
| - } | ||
| - | ||
| - return this.snitch; | ||
| - } | ||
| - | ||
| - private Thread getSnitchThread() { | ||
| - return this.snitchThread; | ||
| - } | ||
| - | ||
| - private void setSnitchThread( final Thread thread ) { | ||
| - this.snitchThread = thread; | ||
| - } | ||
| - | ||
| - private synchronized Options getOptions() { | ||
| - if( this.options == null ) { | ||
| - this.options = Services.load( Options.class ); | ||
| - } | ||
| - | ||
| - return this.options; | ||
| - } | ||
| - | ||
| - private Scene getScene() { | ||
| - return getMainWindow().getScene(); | ||
| - } | ||
| - | ||
| - private MainWindow getMainWindow() { | ||
| - return this.mainWindow; | ||
| - } | ||
| - | ||
| - private String getApplicationTitle() { | ||
| - return get( "Main.title" ); | ||
| - } | ||
| - | ||
| - private static Application getApplication() { | ||
| - return Main.app; | ||
| - } | ||
| - | ||
| - private Image createImage( final String filename ) { | ||
| - return new Image( filename ); | ||
| + MainFx.main( args ); | ||
| } | ||
| } | ||
| +/* | ||
| + * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | ||
| + * | ||
| + * All rights reserved. | ||
| + * | ||
| + * Redistribution and use in source and binary forms, with or without | ||
| + * modification, are permitted provided that the following conditions are met: | ||
| + * | ||
| + * o Redistributions of source code must retain the above copyright | ||
| + * notice, this list of conditions and the following disclaimer. | ||
| + * | ||
| + * o Redistributions in binary form must reproduce the above copyright | ||
| + * notice, this list of conditions and the following disclaimer in the | ||
| + * documentation and/or other materials provided with the distribution. | ||
| + * | ||
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| + */ | ||
| +package com.scrivenvar; | ||
| + | ||
| +import com.scrivenvar.preferences.FilePreferencesFactory; | ||
| +import com.scrivenvar.service.Options; | ||
| +import com.scrivenvar.service.Snitch; | ||
| +import com.scrivenvar.service.events.Notifier; | ||
| +import com.scrivenvar.util.StageState; | ||
| +import javafx.application.Application; | ||
| +import javafx.scene.Scene; | ||
| +import javafx.scene.image.Image; | ||
| +import javafx.stage.Stage; | ||
| + | ||
| +import static com.scrivenvar.Constants.*; | ||
| +import static com.scrivenvar.Messages.get; | ||
| + | ||
| +/** | ||
| + * Application entry point. The application allows users to edit Markdown | ||
| + * files and see a real-time preview of the edits. | ||
| + * | ||
| + * @author Karl Tauber and White Magic Software, Ltd. | ||
| + */ | ||
| +public final class MainFx extends Application { | ||
| + | ||
| + private static Application sApplication; | ||
| + | ||
| + private Options mOptions; | ||
| + private Snitch mSnitch; | ||
| + private Thread mSnitchThread; | ||
| + private StageState mStageState; | ||
| + | ||
| + private final MainWindow mainWindow = new MainWindow(); | ||
| + | ||
| + public static void main( final String[] args ) { | ||
| + initLogger(); | ||
| + initPreferences(); | ||
| + launch( args ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Prevent | ||
| + * <a href="http://stackoverflow.com/a/41476462/59087">standard error logging</a>. | ||
| + */ | ||
| + private static void initLogger() { | ||
| + //LogManager.getLogManager().reset(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Sets the factory used for reading user preferences. | ||
| + */ | ||
| + private static void initPreferences() { | ||
| + System.setProperty( | ||
| + "java.util.prefs.PreferencesFactory", | ||
| + FilePreferencesFactory.class.getName() | ||
| + ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Application entry point. | ||
| + * | ||
| + * @param stage The primary application stage. | ||
| + */ | ||
| + @Override | ||
| + public void start( final Stage stage ) { | ||
| + initApplication(); | ||
| + initNotifyService(); | ||
| + initState( stage ); | ||
| + initStage( stage ); | ||
| + initSnitch(); | ||
| + | ||
| + stage.show(); | ||
| + } | ||
| + | ||
| + public static void showDocument( final String uri ) { | ||
| + getApplication().getHostServices().showDocument( uri ); | ||
| + } | ||
| + | ||
| + private void initApplication() { | ||
| + sApplication = this; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Constructs the notify service and appends the main window to the list of | ||
| + * notification observers. | ||
| + */ | ||
| + private void initNotifyService() { | ||
| + final Notifier notifier = Services.load( Notifier.class ); | ||
| + notifier.addObserver( getMainWindow() ); | ||
| + } | ||
| + | ||
| + private void initState( final Stage stage ) { | ||
| + mStageState = new StageState( stage, getmOptions().getState() ); | ||
| + } | ||
| + | ||
| + private void initStage( final Stage stage ) { | ||
| + stage.getIcons().addAll( | ||
| + createImage( FILE_LOGO_16 ), | ||
| + createImage( FILE_LOGO_32 ), | ||
| + createImage( FILE_LOGO_128 ), | ||
| + createImage( FILE_LOGO_256 ), | ||
| + createImage( FILE_LOGO_512 ) ); | ||
| + stage.setTitle( getApplicationTitle() ); | ||
| + stage.setScene( getScene() ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Watch for file system changes. | ||
| + */ | ||
| + private void initSnitch() { | ||
| + setmSnitchThread( new Thread( getmSnitch() ) ); | ||
| + getmSnitchThread().start(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Stops the snitch service, if its running. | ||
| + * | ||
| + * @throws InterruptedException Couldn't stop the snitch thread. | ||
| + */ | ||
| + @Override | ||
| + public void stop() throws InterruptedException { | ||
| + getmSnitch().stop(); | ||
| + | ||
| + final Thread thread = getmSnitchThread(); | ||
| + | ||
| + if( thread != null ) { | ||
| + thread.interrupt(); | ||
| + thread.join(); | ||
| + } | ||
| + } | ||
| + | ||
| + private synchronized Snitch getmSnitch() { | ||
| + if( this.mSnitch == null ) { | ||
| + this.mSnitch = Services.load( Snitch.class ); | ||
| + } | ||
| + | ||
| + return this.mSnitch; | ||
| + } | ||
| + | ||
| + private Thread getmSnitchThread() { | ||
| + return this.mSnitchThread; | ||
| + } | ||
| + | ||
| + private void setmSnitchThread( final Thread thread ) { | ||
| + this.mSnitchThread = thread; | ||
| + } | ||
| + | ||
| + private synchronized Options getmOptions() { | ||
| + if( this.mOptions == null ) { | ||
| + this.mOptions = Services.load( Options.class ); | ||
| + } | ||
| + | ||
| + return this.mOptions; | ||
| + } | ||
| + | ||
| + private Scene getScene() { | ||
| + return getMainWindow().getScene(); | ||
| + } | ||
| + | ||
| + private MainWindow getMainWindow() { | ||
| + return this.mainWindow; | ||
| + } | ||
| + | ||
| + private String getApplicationTitle() { | ||
| + return get( "Main.title" ); | ||
| + } | ||
| + | ||
| + private static Application getApplication() { | ||
| + return sApplication; | ||
| + } | ||
| + | ||
| + private Image createImage( final String filename ) { | ||
| + return new Image( filename ); | ||
| + } | ||
| +} | ||
| package com.scrivenvar.controls; | ||
| -import com.scrivenvar.Main; | ||
| +import com.scrivenvar.MainFx; | ||
| import javafx.beans.property.SimpleStringProperty; | ||
| import javafx.beans.property.StringProperty; | ||
| @Override | ||
| public void fire() { | ||
| - Main.showDocument( getUri() ); | ||
| + MainFx.showDocument( getUri() ); | ||
| } | ||
| package com.scrivenvar.editors.markdown; | ||
| -import static com.scrivenvar.Constants.STYLESHEET_MARKDOWN; | ||
| import com.scrivenvar.dialogs.ImageDialog; | ||
| import com.scrivenvar.dialogs.LinkDialog; | ||
| import com.scrivenvar.editors.EditorPane; | ||
| import com.scrivenvar.processors.MarkdownProcessor; | ||
| -import static com.scrivenvar.util.Utils.ltrim; | ||
| -import static com.scrivenvar.util.Utils.rtrim; | ||
| import com.vladsch.flexmark.ast.Link; | ||
| -import java.nio.file.Path; | ||
| -import java.util.regex.Matcher; | ||
| -import java.util.regex.Pattern; | ||
| - | ||
| import com.vladsch.flexmark.util.ast.Node; | ||
| -import javafx.beans.value.ObservableValue; | ||
| import javafx.scene.control.Dialog; | ||
| import javafx.scene.control.IndexRange; | ||
| -import static javafx.scene.input.KeyCode.ENTER; | ||
| import javafx.scene.input.KeyEvent; | ||
| import javafx.stage.Window; | ||
| import org.fxmisc.richtext.StyleClassedTextArea; | ||
| + | ||
| +import java.nio.file.Path; | ||
| +import java.util.regex.Matcher; | ||
| +import java.util.regex.Pattern; | ||
| + | ||
| +import static com.scrivenvar.Constants.STYLESHEET_MARKDOWN; | ||
| +import static com.scrivenvar.util.Utils.ltrim; | ||
| +import static com.scrivenvar.util.Utils.rtrim; | ||
| +import static javafx.scene.input.KeyCode.ENTER; | ||
| import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | ||
| private static final Pattern AUTO_INDENT_PATTERN = Pattern.compile( | ||
| - "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" ); | ||
| + "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" ); | ||
| public MarkdownEditorPane() { | ||
| addKeyboardListener( keyPressed( ENTER ), this::enterPressed ); | ||
| - } | ||
| - | ||
| - public ObservableValue<String> markdownProperty() { | ||
| - return getEditor().textProperty(); | ||
| } | ||
| private void enterPressed( final KeyEvent e ) { | ||
| final StyleClassedTextArea textArea = getEditor(); | ||
| - final String currentLine = textArea.getText( textArea.getCurrentParagraph() ); | ||
| + final String currentLine = | ||
| + textArea.getText( textArea.getCurrentParagraph() ); | ||
| final Matcher matcher = AUTO_INDENT_PATTERN.matcher( currentLine ); | ||
| String newText = "\n"; | ||
| if( matcher.matches() ) { | ||
| if( !matcher.group( 2 ).isEmpty() ) { | ||
| - // indent new line with same whitespace characters and list markers as current line | ||
| + // indent new line with same whitespace characters and list markers | ||
| + // as current line | ||
| newText = newText.concat( matcher.group( 1 ) ); | ||
| } | ||
| else { | ||
| // current line contains only whitespace characters and list markers | ||
| // --> empty current line | ||
| final int caretPosition = textArea.getCaretPosition(); | ||
| - textArea.selectRange( caretPosition - currentLine.length(), caretPosition ); | ||
| + textArea.selectRange( caretPosition - currentLine.length(), | ||
| + caretPosition ); | ||
| } | ||
| } | ||
| } | ||
| - public void surroundSelection( String leading, String trailing, final String hint ) { | ||
| + public void surroundSelection( String leading, String trailing, | ||
| + final String hint ) { | ||
| final StyleClassedTextArea textArea = getEditor(); | ||
| } | ||
| - // remove trailing whitespaces from trailing text if selection ends at text end | ||
| + // remove trailing whitespaces from trailing text if selection ends at | ||
| + // text end | ||
| if( end == textArea.getLength() ) { | ||
| trailing = rtrim( trailing ); | ||
| // replace text and update selection | ||
| - textArea.replaceText( start, end, leading + trimmedSelectedText + trailing ); | ||
| + textArea.replaceText( start, | ||
| + end, | ||
| + leading + trimmedSelectedText + trailing ); | ||
| textArea.selectRange( selStart, selEnd ); | ||
| } | ||
| /** | ||
| * Returns one of: selected text, word under cursor, or parsed hyperlink from | ||
| * the markdown AST. | ||
| * | ||
| - * @return | ||
| + * @return An instance containing the link URL and display text. | ||
| */ | ||
| private HyperlinkModel getHyperlink() { | ||
| } | ||
| - final HyperlinkModel model = createHyperlinkModel( | ||
| - link, selectedText, "https://website.com" | ||
| + return createHyperlinkModel( | ||
| + link, selectedText, "https://localhost" | ||
| ); | ||
| - | ||
| - return model; | ||
| } | ||
| + @SuppressWarnings("SameParameterValue") | ||
| private HyperlinkModel createHyperlinkModel( | ||
| - final Link link, final String selection, final String url ) { | ||
| + final Link link, final String selection, final String url ) { | ||
| return link == null | ||
| - ? new HyperlinkModel( selection, url ) | ||
| - : new HyperlinkModel( link ); | ||
| + ? new HyperlinkModel( selection, url ) | ||
| + : new HyperlinkModel( link ); | ||
| } | ||
| private void insertObject( final Dialog<String> dialog ) { | ||
| - dialog.showAndWait().ifPresent( result -> { | ||
| - getEditor().replaceSelection( result ); | ||
| - } ); | ||
| + dialog.showAndWait().ifPresent( | ||
| + result -> getEditor().replaceSelection( result ) | ||
| + ); | ||
| } | ||
| import static java.lang.Character.isLetter; | ||
| import static java.lang.Math.min; | ||
| + | ||
| import javafx.beans.value.ObservableValue; | ||
| * | ||
| * @param processor The next processor in the chain. | ||
| - * @param position The caret's current position in the text. | ||
| + * @param position The caret's current position in the text. | ||
| */ | ||
| public MarkdownCaretInsertionProcessor( | ||
| - final Processor<String> processor, | ||
| - final ObservableValue<Integer> position ) { | ||
| + final Processor<String> processor, | ||
| + final ObservableValue<Integer> position ) { | ||
| super( processor, position ); | ||
| } | ||
| /** | ||
| * Changes the text to insert a "caret" at the caret position. This will | ||
| * insert the unique key of Constants.MD_CARET_POSITION into the document. | ||
| * | ||
| * @param t The text document to process. | ||
| - * | ||
| * @return The text with the caret position token inserted at the caret | ||
| * position. | ||
| import com.scrivenvar.Services; | ||
| import com.scrivenvar.service.Snitch; | ||
| -import java.io.File; | ||
| -import java.io.IOException; | ||
| -import java.io.Reader; | ||
| -import java.io.StringReader; | ||
| -import java.io.StringWriter; | ||
| -import java.nio.file.Path; | ||
| -import java.nio.file.Paths; | ||
| -import java.text.ParseException; | ||
| +import net.sf.saxon.TransformerFactoryImpl; | ||
| +import net.sf.saxon.trans.XPathException; | ||
| + | ||
| import javax.xml.stream.XMLEventReader; | ||
| import javax.xml.stream.XMLInputFactory; | ||
| import javax.xml.stream.XMLStreamException; | ||
| import javax.xml.stream.events.ProcessingInstruction; | ||
| import javax.xml.stream.events.XMLEvent; | ||
| -import javax.xml.transform.ErrorListener; | ||
| -import javax.xml.transform.Source; | ||
| -import javax.xml.transform.Transformer; | ||
| -import javax.xml.transform.TransformerConfigurationException; | ||
| -import javax.xml.transform.TransformerException; | ||
| -import javax.xml.transform.TransformerFactory; | ||
| +import javax.xml.transform.*; | ||
| import javax.xml.transform.stream.StreamResult; | ||
| import javax.xml.transform.stream.StreamSource; | ||
| -import net.sf.saxon.TransformerFactoryImpl; | ||
| +import java.io.File; | ||
| +import java.io.Reader; | ||
| +import java.io.StringReader; | ||
| +import java.io.StringWriter; | ||
| +import java.nio.file.Path; | ||
| +import java.nio.file.Paths; | ||
| + | ||
| import static net.sf.saxon.tree.util.ProcInstParser.getPseudoAttribute; | ||
| /** | ||
| * Transforms an XML document. The XML document must have a stylesheet specified | ||
| * as part of its processing instructions, such as: | ||
| * | ||
| * <code>xml-stylesheet type="text/xsl" href="markdown.xsl"</code> | ||
| - * | ||
| + * <p> | ||
| * The XSL must transform the XML document into Markdown, or another format | ||
| * recognized by the next link on the chain. | ||
| * | ||
| * @author White Magic Software, Ltd. | ||
| */ | ||
| public class XMLProcessor extends AbstractProcessor<String> | ||
| - implements ErrorListener { | ||
| + implements ErrorListener { | ||
| private final Snitch snitch = Services.load( Snitch.class ); | ||
| * | ||
| * @param processor Next link in the processing chain. | ||
| - * @param path The path to the XML file content to be processed. | ||
| + * @param path The path to the XML file content to be processed. | ||
| */ | ||
| public XMLProcessor( final Processor<String> processor, final Path path ) { | ||
| * | ||
| * @param text The text to transform, can be empty, cannot be null. | ||
| - * | ||
| * @return The transformed text, or empty if text is empty. | ||
| */ | ||
| * | ||
| * @param text The text to transform. | ||
| - * | ||
| * @return The transformed text. | ||
| */ | ||
| private String transform( final String text ) throws Exception { | ||
| // Extract the XML stylesheet processing instruction. | ||
| final String template = getXsltFilename( text ); | ||
| final Path xsl = getXslPath( template ); | ||
| try( | ||
| - final StringWriter output = new StringWriter( text.length() ); | ||
| - final StringReader input = new StringReader( text ) ) { | ||
| + final StringWriter output = new StringWriter( text.length() ); | ||
| + final StringReader input = new StringReader( text ) ) { | ||
| // Listen for external file modification events. | ||
| getSnitch().listen( xsl ); | ||
| getTransformer( xsl ).transform( | ||
| - new StreamSource( input ), | ||
| - new StreamResult( output ) | ||
| + new StreamSource( input ), | ||
| + new StreamResult( output ) | ||
| ); | ||
| * | ||
| * @param xsl The path to an XSLT file. | ||
| - * | ||
| * @return A transformer that will transform XML documents using the given | ||
| * XSLT file. | ||
| - * | ||
| * @throws TransformerConfigurationException Could not instantiate the | ||
| - * transformer. | ||
| + * transformer. | ||
| */ | ||
| private Transformer getTransformer( final Path xsl ) | ||
| - throws TransformerConfigurationException, IOException { | ||
| + throws TransformerConfigurationException { | ||
| if( this.transformer == null ) { | ||
| this.transformer = createTransformer( xsl ); | ||
| * | ||
| * @param xsl The stylesheet to use for transforming XML documents. | ||
| - * | ||
| * @return The edited XML document transformed into another format (usually | ||
| * markdown). | ||
| - * | ||
| * @throws TransformerConfigurationException Could not create the transformer. | ||
| */ | ||
| protected Transformer createTransformer( final Path xsl ) | ||
| - throws TransformerConfigurationException { | ||
| + throws TransformerConfigurationException { | ||
| final Source xslt = new StreamSource( xsl.toFile() ); | ||
| * | ||
| * @param xml The XML containing an xml-stylesheet processing instruction. | ||
| - * | ||
| * @return The href pseudo-attribute value. | ||
| - * | ||
| * @throws XMLStreamException Could not parse the XML file. | ||
| - * @throws ParseException Could not find a non-empty HREF attribute value. | ||
| */ | ||
| private String getXsltFilename( final String xml ) | ||
| - throws XMLStreamException, ParseException { | ||
| + throws XMLStreamException, XPathException { | ||
| String result = ""; | ||
| if( event.isProcessingInstruction() ) { | ||
| - final ProcessingInstruction pi = (ProcessingInstruction)event; | ||
| + final ProcessingInstruction pi = (ProcessingInstruction) event; | ||
| final String target = pi.getTarget(); | ||
| private XMLEventReader createXMLEventReader( final Reader reader ) | ||
| - throws XMLStreamException { | ||
| + throws XMLStreamException { | ||
| return getXMLInputFactory().createXMLEventReader( reader ); | ||
| } | ||
| Delta | 963 lines added, 782 lines removed, 181-line increase |
|---|