| 1 | # always use LF line endings | |
| 2 | ||
| 3 | # ALL FILES: | |
| 4 | # Normalize line endings to LF on checkin and | |
| 5 | # prevent conversion to CRLF when the file is checked out. | |
| 6 | ||
| 7 | * text eol=lf | |
| 8 | ||
| 9 | ||
| 10 | # BINARY FILES: | |
| 11 | # Disable line ending normalize on checkin. | |
| 12 | ||
| 13 | *.gif binary | |
| 14 | *.jar binary | |
| 15 | *.png binary | |
| 16 | *.zip binary | |
| 17 | *.ttf binary | |
| 18 | *.otf binary | |
| 19 | *.blend binary | |
| 20 | ||
| 1 | 21 |
| 1 | --- | |
| 2 | name: Bug report | |
| 3 | about: Create a report to help us improve | |
| 4 | title: '' | |
| 5 | labels: bug | |
| 6 | assignees: '' | |
| 7 | ||
| 8 | --- | |
| 9 | ||
| 10 | **Description** | |
| 11 | A concise problem description. | |
| 12 | ||
| 13 | **Replicate** | |
| 14 | Exact and complete steps to reproduce the problem 100% of the time: | |
| 15 | ||
| 16 | 1. Open '...' | |
| 17 | 1. Click '....' | |
| 18 | 1. Click '....' | |
| 19 | ||
| 20 | **Expected** | |
| 21 | Describe the expected behaviour. | |
| 22 | ||
| 23 | **Actual** | |
| 24 | Describe the actual behaviour. | |
| 25 | ||
| 26 | **Screenshots** | |
| 27 | Add screenshots to show the problem, if applicable. | |
| 28 | ||
| 29 | **Environment** | |
| 30 | - Operating System: (Windows, Linux, Mac) | |
| 31 | - Application: e.g., 1.7.16 | |
| 32 | ||
| 33 | **Details** | |
| 34 | Add additional information, if applicable. | |
| 1 | 35 |
| 1 | dist | |
| 2 | scrivenvar.bin | |
| 3 | scrivenvar.exe | |
| 4 | build | |
| 5 | .gradle | |
| 6 | contacted.csv | |
| 7 | video | |
| 8 | .settings | |
| 9 | .classpath | |
| 1 | 10 |
| 1 | workspace.xml | |
| 1 | 2 |
| 1 | ||
| 1 | <component name="ProjectCodeStyleConfiguration"> | |
| 2 | <state> | |
| 3 | <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> | |
| 4 | </state> | |
| 5 | </component> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="CompilerConfiguration"> | |
| 4 | <bytecodeTargetLevel target="14" /> | |
| 5 | </component> | |
| 6 | <component name="JavacSettings"> | |
| 7 | <option name="ADDITIONAL_OPTIONS_OVERRIDE"> | |
| 8 | <module name="scrivenvar.main" options="--add-exports java.desktop/sun.swing=ALL-UNNAMED" /> | |
| 9 | </option> | |
| 10 | </component> | |
| 11 | </project> |
| 1 | ||
| 1 | <component name="ProjectDictionaryState"> | |
| 2 | <dictionary name="jarvisd"> | |
| 3 | <words> | |
| 4 | <w>blockquotes</w> | |
| 5 | <w>sigil</w> | |
| 6 | <w>transcoded</w> | |
| 7 | </words> | |
| 8 | </dictionary> | |
| 9 | </component> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="GradleMigrationSettings" migrationVersion="1" /> | |
| 4 | <component name="GradleSettings"> | |
| 5 | <option name="linkedExternalProjectsSettings"> | |
| 6 | <GradleProjectSettings> | |
| 7 | <option name="distributionType" value="LOCAL" /> | |
| 8 | <option name="externalProjectPath" value="$PROJECT_DIR$" /> | |
| 9 | <option name="gradleHome" value="/opt/gradle" /> | |
| 10 | <option name="gradleJvm" value="#JAVA_HOME" /> | |
| 11 | <option name="modules"> | |
| 12 | <set> | |
| 13 | <option value="$PROJECT_DIR$" /> | |
| 14 | </set> | |
| 15 | </option> | |
| 16 | </GradleProjectSettings> | |
| 17 | </option> | |
| 18 | </component> | |
| 19 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RemoteRepositoriesConfiguration"> | |
| 4 | <remote-repository> | |
| 5 | <option name="id" value="central" /> | |
| 6 | <option name="name" value="Maven Central repository" /> | |
| 7 | <option name="url" value="https://repo1.maven.org/maven2" /> | |
| 8 | </remote-repository> | |
| 9 | <remote-repository> | |
| 10 | <option name="id" value="jboss.community" /> | |
| 11 | <option name="name" value="JBoss Community repository" /> | |
| 12 | <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> | |
| 13 | </remote-repository> | |
| 14 | <remote-repository> | |
| 15 | <option name="id" value="MavenRepo" /> | |
| 16 | <option name="name" value="MavenRepo" /> | |
| 17 | <option name="url" value="https://repo.maven.apache.org/maven2/" /> | |
| 18 | </remote-repository> | |
| 19 | <remote-repository> | |
| 20 | <option name="id" value="maven" /> | |
| 21 | <option name="name" value="maven" /> | |
| 22 | <option name="url" value="https://oss.sonatype.org/content/repositories/snapshots/" /> | |
| 23 | </remote-repository> | |
| 24 | <remote-repository> | |
| 25 | <option name="id" value="BintrayJCenter" /> | |
| 26 | <option name="name" value="BintrayJCenter" /> | |
| 27 | <option name="url" value="https://jcenter.bintray.com/" /> | |
| 28 | </remote-repository> | |
| 29 | <remote-repository> | |
| 30 | <option name="id" value="maven2" /> | |
| 31 | <option name="name" value="maven2" /> | |
| 32 | <option name="url" value="https://nexus.bedatadriven.com/content/groups/public" /> | |
| 33 | </remote-repository> | |
| 34 | </component> | |
| 35 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="ExternalStorageConfigurationManager" enabled="true" /> | |
| 4 | <component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14" project-jdk-type="JavaSDK"> | |
| 5 | <output url="file://$PROJECT_DIR$/build" /> | |
| 6 | </component> | |
| 7 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RGraphicsSettings"> | |
| 4 | <option name="height" value="1600" /> | |
| 5 | <option name="resolution" value="112" /> | |
| 6 | <option name="version" value="1" /> | |
| 7 | <option name="width" value="2560" /> | |
| 8 | </component> | |
| 9 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RSettings"> | |
| 4 | <option name="interpreterPath" value="/usr/bin/R" /> | |
| 5 | </component> | |
| 6 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="RPackageService"> | |
| 4 | <option name="enabledRepositoryUrls"> | |
| 5 | <list> | |
| 6 | <option value="@CRAN@" /> | |
| 7 | </list> | |
| 8 | </option> | |
| 9 | </component> | |
| 10 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <module type="JAVA_MODULE" version="4" /> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="Palette2"> | |
| 4 | <group name="Swing"> | |
| 5 | <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"> | |
| 6 | <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> | |
| 7 | </item> | |
| 8 | <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"> | |
| 9 | <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> | |
| 10 | </item> | |
| 11 | <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 12 | <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> | |
| 13 | </item> | |
| 14 | <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> | |
| 15 | <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> | |
| 16 | </item> | |
| 17 | <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 18 | <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> | |
| 19 | <initial-values> | |
| 20 | <property name="text" value="Button" /> | |
| 21 | </initial-values> | |
| 22 | </item> | |
| 23 | <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 24 | <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | |
| 25 | <initial-values> | |
| 26 | <property name="text" value="RadioButton" /> | |
| 27 | </initial-values> | |
| 28 | </item> | |
| 29 | <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 30 | <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> | |
| 31 | <initial-values> | |
| 32 | <property name="text" value="CheckBox" /> | |
| 33 | </initial-values> | |
| 34 | </item> | |
| 35 | <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 36 | <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> | |
| 37 | <initial-values> | |
| 38 | <property name="text" value="Label" /> | |
| 39 | </initial-values> | |
| 40 | </item> | |
| 41 | <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 42 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | |
| 43 | <preferred-size width="150" height="-1" /> | |
| 44 | </default-constraints> | |
| 45 | </item> | |
| 46 | <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 47 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | |
| 48 | <preferred-size width="150" height="-1" /> | |
| 49 | </default-constraints> | |
| 50 | </item> | |
| 51 | <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 52 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> | |
| 53 | <preferred-size width="150" height="-1" /> | |
| 54 | </default-constraints> | |
| 55 | </item> | |
| 56 | <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 57 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 58 | <preferred-size width="150" height="50" /> | |
| 59 | </default-constraints> | |
| 60 | </item> | |
| 61 | <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 62 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 63 | <preferred-size width="150" height="50" /> | |
| 64 | </default-constraints> | |
| 65 | </item> | |
| 66 | <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 67 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 68 | <preferred-size width="150" height="50" /> | |
| 69 | </default-constraints> | |
| 70 | </item> | |
| 71 | <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 72 | <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> | |
| 73 | </item> | |
| 74 | <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 75 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 76 | <preferred-size width="150" height="50" /> | |
| 77 | </default-constraints> | |
| 78 | </item> | |
| 79 | <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 80 | <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> | |
| 81 | <preferred-size width="150" height="50" /> | |
| 82 | </default-constraints> | |
| 83 | </item> | |
| 84 | <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 85 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> | |
| 86 | <preferred-size width="150" height="50" /> | |
| 87 | </default-constraints> | |
| 88 | </item> | |
| 89 | <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 90 | <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | |
| 91 | <preferred-size width="200" height="200" /> | |
| 92 | </default-constraints> | |
| 93 | </item> | |
| 94 | <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 95 | <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> | |
| 96 | <preferred-size width="200" height="200" /> | |
| 97 | </default-constraints> | |
| 98 | </item> | |
| 99 | <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> | |
| 100 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | |
| 101 | </item> | |
| 102 | <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 103 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> | |
| 104 | </item> | |
| 105 | <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 106 | <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> | |
| 107 | </item> | |
| 108 | <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 109 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> | |
| 110 | </item> | |
| 111 | <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 112 | <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> | |
| 113 | <preferred-size width="-1" height="20" /> | |
| 114 | </default-constraints> | |
| 115 | </item> | |
| 116 | <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> | |
| 117 | <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> | |
| 118 | </item> | |
| 119 | <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> | |
| 120 | <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> | |
| 121 | </item> | |
| 122 | </group> | |
| 123 | </component> | |
| 124 | </project> |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <project version="4"> | |
| 3 | <component name="VcsDirectoryMappings"> | |
| 4 | <mapping directory="$PROJECT_DIR$" vcs="Git" /> | |
| 5 | </component> | |
| 6 | </project> |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <projectDescription> | |
| 3 | <name>Markdown Writer FX</name> | |
| 4 | <comment></comment> | |
| 5 | <projects> | |
| 6 | </projects> | |
| 7 | <buildSpec> | |
| 8 | <buildCommand> | |
| 9 | <name>org.eclipse.jdt.core.javabuilder</name> | |
| 10 | <arguments> | |
| 11 | </arguments> | |
| 12 | </buildCommand> | |
| 13 | <buildCommand> | |
| 14 | <name>org.eclipse.buildship.core.gradleprojectbuilder</name> | |
| 15 | <arguments> | |
| 16 | </arguments> | |
| 17 | </buildCommand> | |
| 18 | </buildSpec> | |
| 19 | <natures> | |
| 20 | <nature>org.eclipse.jdt.core.javanature</nature> | |
| 21 | <nature>org.eclipse.buildship.core.gradleprojectnature</nature> | |
| 22 | </natures> | |
| 23 | <filteredResources> | |
| 24 | <filter> | |
| 25 | <id>1438449113801</id> | |
| 26 | <name></name> | |
| 27 | <type>26</type> | |
| 28 | <matcher> | |
| 29 | <id>org.eclipse.ui.ide.multiFilter</id> | |
| 30 | <arguments>1.0-projectRelativePath-matches-false-false-build</arguments> | |
| 31 | </matcher> | |
| 32 | </filter> | |
| 33 | <filter> | |
| 34 | <id>1438449113801</id> | |
| 35 | <name></name> | |
| 36 | <type>26</type> | |
| 37 | <matcher> | |
| 38 | <id>org.eclipse.ui.ide.multiFilter</id> | |
| 39 | <arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments> | |
| 40 | </matcher> | |
| 41 | </filter> | |
| 42 | </filteredResources> | |
| 43 | </projectDescription> | |
| 1 | 44 |
| 1 | language: java | |
| 2 | ||
| 3 | jdk: | |
| 4 | - oraclejdk8 | |
| 5 | ||
| 6 | # enable Java 8u45+, see https://github.com/travis-ci/travis-ci/issues/4042 | |
| 7 | addons: | |
| 8 | apt: | |
| 9 | packages: | |
| 10 | - oracle-java8-installer | |
| 11 | os: | |
| 12 | - linux | |
| 13 | ||
| 14 | # run in container | |
| 15 | sudo: false | |
| 1 | 16 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to build the application and platform binaries. | |
| 4 | ||
| 5 | # Requirements | |
| 6 | ||
| 7 | Download and install the following software packages: | |
| 8 | ||
| 9 | * [OpenJDK 14](https://openjdk.java.net) | |
| 10 | * [Gradle 6.4](https://gradle.org/releases) | |
| 11 | ||
| 12 | # Build | |
| 13 | ||
| 14 | Build the application überjar as follows: | |
| 15 | ||
| 16 | gradle clean jar | |
| 17 | ||
| 18 | The application is built. | |
| 19 | ||
| 20 | # Run | |
| 21 | ||
| 22 | After the application is compiled, run it as follows: | |
| 23 | ||
| 24 | java -jar build/libs/scrivenvar.jar | |
| 25 | ||
| 26 | On Windows: | |
| 27 | ||
| 28 | java -jar build\libs\scrivenvar.jar | |
| 29 | ||
| 30 | # Installers | |
| 31 | ||
| 32 | This section describes how to set up the development environment and | |
| 33 | build native executables for supported operating systems. | |
| 34 | ||
| 35 | ## Setup | |
| 36 | ||
| 37 | Follow these one-time setup instructions to begin: | |
| 38 | ||
| 39 | 1. Ensure `$HOME/bin` is set in the `PATH` environment variable. | |
| 40 | 1. Move `build-template` into `$HOME/bin`. | |
| 41 | ||
| 42 | Setup is complete. | |
| 43 | ||
| 44 | ## Binaries | |
| 45 | ||
| 46 | Run the `installer` script to build platform-specific binaries, such as: | |
| 47 | ||
| 48 | ./installer -V -o linux | |
| 49 | ||
| 50 | The `installer` script: | |
| 51 | ||
| 52 | * downloads a JDK; | |
| 53 | * generates a run script; | |
| 54 | * bundles the JDK, run script, and JAR file; and | |
| 55 | * creates a standalone binary, so no installation required. | |
| 56 | ||
| 57 | Run `./installer -h` to see all command-line options. | |
| 58 | ||
| 59 | # Versioning | |
| 60 | ||
| 61 | Version numbers are read directly from Git using a plugin. The version | |
| 62 | number is written to `app.properties`, a properties file in the `resources` | |
| 63 | directory that can be read from within the application. | |
| 64 | ||
| 1 | 65 |
| 1 | # License | |
| 2 | ||
| 3 | Copyright 2015 Karl Tauber | |
| 4 | All rights reserved. | |
| 5 | ||
| 6 | Copyright 2020 White Magic Software, Ltd. | |
| 7 | All rights reserved. | |
| 8 | ||
| 9 | Redistribution and use in source and binary forms, with or without | |
| 10 | modification, are permitted provided that the following conditions are met: | |
| 11 | ||
| 12 | * Redistributions of source code must retain the above copyright | |
| 13 | notice, this list of conditions and the following disclaimer. | |
| 14 | ||
| 15 | * Redistributions in binary form must reproduce the above copyright | |
| 16 | notice, this list of conditions and the following disclaimer in the | |
| 17 | documentation and/or other materials provided with the distribution. | |
| 18 | ||
| 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 30 |
| 1 | # R Functions | |
| 2 | ||
| 3 | Import the files in this directory into the application, which include: | |
| 4 | ||
| 5 | * pluralize.R | |
| 6 | * possessive.R | |
| 7 | * conversion.R | |
| 8 | * csv.R | |
| 9 | ||
| 10 | # pluralize.R | |
| 11 | ||
| 12 | This file defines a function that implements most of Damian Conway's [An Algorithmic Approach to English Pluralization](http://blob.perl.org/tpc/1998/User_Applications/Algorithmic%20Approach%20Plurals/Algorithmic_Plurals.html). | |
| 13 | ||
| 14 | ## Usage | |
| 15 | ||
| 16 | Example usages of the pluralize function include: | |
| 17 | ||
| 18 | `r#pluralize( 'mouse' )` - mice | |
| 19 | `r#pluralize( 'buzz' )` - buzzes | |
| 20 | `r#pluralize( 'bus' )` - busses | |
| 21 | ||
| 22 | # possessive.R | |
| 23 | ||
| 24 | This file defines a function that applies possessives to English words. | |
| 25 | ||
| 26 | ## Usage | |
| 27 | ||
| 28 | Example usages of the possessive function include: | |
| 29 | ||
| 30 | `r#pos( 'Ross' )` - Ross' | |
| 31 | `r#pos( 'Ruby' )` - Ruby's | |
| 32 | `r#pos( 'Lois' )` - Lois' | |
| 33 | `r#pos( 'my' )` - mine | |
| 34 | `r#pos( 'Your' )` - Yours | |
| 35 | ||
| 1 | 36 |
| 1 | setwd( '$application.r.working.directory$' ) | |
| 2 | assign( "anchor", '$date.anchor$', envir = .GlobalEnv ) | |
| 3 | ||
| 4 | source( 'pluralize.R' ) | |
| 5 | source( 'possessive.R' ) | |
| 6 | source( 'conversion.R' ) | |
| 7 | source( 'csv.R' ) | |
| 8 | ||
| 1 | 9 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Substitute R expressions in a document with their evaluated value. The | |
| 26 | # anchor variable must be set for functions that use relative dates. | |
| 27 | # ----------------------------------------------------------------------------- | |
| 28 | ||
| 29 | # ----------------------------------------------------------------------------- | |
| 30 | # Evaluates an expression; writes s if there is no expression. | |
| 31 | # ----------------------------------------------------------------------------- | |
| 32 | x <- function( s ) { | |
| 33 | tryCatch( { | |
| 34 | r = eval( parse( text = s ) ) | |
| 35 | ||
| 36 | # If the result isn't primitive, then it was probably parsed into | |
| 37 | # an unprintable object (e.g., "gray" becomes a colour). In those | |
| 38 | # cases, return the original text string. Otherwise, an atomic | |
| 39 | # value means a primitive type (string, integer, etc.) that can be | |
| 40 | # written directly into the document. | |
| 41 | ifelse( is.atomic( r ), r, s ); | |
| 42 | }, | |
| 43 | warning = function( w ) { s }, | |
| 44 | error = function( e ) { s } ) | |
| 45 | } | |
| 46 | ||
| 47 | # ----------------------------------------------------------------------------- | |
| 48 | # Returns a date offset by a given number of days, relative to the given | |
| 49 | # date (d). This does not use the anchor, but is used to get the anchor's | |
| 50 | # value as a date. | |
| 51 | # ----------------------------------------------------------------------------- | |
| 52 | when <- function( d, n = 0, format = "%Y-%m-%d" ) { | |
| 53 | as.Date( d, format = format ) + x( n ) | |
| 54 | } | |
| 55 | ||
| 56 | # ----------------------------------------------------------------------------- | |
| 57 | # Full date (s) offset by an optional number of days before or after. | |
| 58 | # This will remove leading zeros (applying leading spaces instead, which | |
| 59 | # are ignored by any worthwhile typesetting engine). | |
| 60 | # ----------------------------------------------------------------------------- | |
| 61 | annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) { | |
| 62 | format( when( anchor, days ), format = oformat ) | |
| 63 | } | |
| 64 | ||
| 65 | # ----------------------------------------------------------------------------- | |
| 66 | # Extracts the year from a date string. | |
| 67 | # ----------------------------------------------------------------------------- | |
| 68 | year <- function( days = 0, format = "%Y-%m-%d" ) { | |
| 69 | annal( days, format, "%Y" ) | |
| 70 | } | |
| 71 | ||
| 72 | # ----------------------------------------------------------------------------- | |
| 73 | # Day of the week (in days since the anchor date). | |
| 74 | # ----------------------------------------------------------------------------- | |
| 75 | weekday <- function( n ) { | |
| 76 | weekdays( when( anchor, n ) ) | |
| 77 | } | |
| 78 | ||
| 79 | # ----------------------------------------------------------------------------- | |
| 80 | # String concatenate function alias because paste0 is a terrible name. | |
| 81 | # ----------------------------------------------------------------------------- | |
| 82 | concat <- paste0 | |
| 83 | ||
| 84 | # ----------------------------------------------------------------------------- | |
| 85 | # Translates a number from digits to words using Chicago Manual of Style. | |
| 86 | # This does not translate numbers greater than one hundred. If ordinal | |
| 87 | # is TRUE, this will return the ordinal name. This will not produce ordinals | |
| 88 | # for numbers greater than 100. | |
| 89 | # ----------------------------------------------------------------------------- | |
| 90 | cms <- function( n, ordinal = FALSE ) { | |
| 91 | n <- x( n ) | |
| 92 | ||
| 93 | if( n == 0 ) { | |
| 94 | if( ordinal ) { | |
| 95 | return( "zeroth" ) | |
| 96 | } | |
| 97 | ||
| 98 | return( "zero" ) | |
| 99 | } | |
| 100 | ||
| 101 | # Concatenate this a little later. | |
| 102 | if( n < 0 ) { | |
| 103 | result = "negative " | |
| 104 | n = abs( n ) | |
| 105 | } | |
| 106 | ||
| 107 | # Do not spell out numbers greater than one hundred. | |
| 108 | if( n > 100 ) { | |
| 109 | # Comma-separated numbers. | |
| 110 | return( commas( n ) ) | |
| 111 | } | |
| 112 | ||
| 113 | # Don't go beyond 100. | |
| 114 | if( n == 100 ) { | |
| 115 | if( ordinal ) { | |
| 116 | return( "one hundredth" ) | |
| 117 | } | |
| 118 | ||
| 119 | return( "one hundred" ) | |
| 120 | } | |
| 121 | ||
| 122 | # Samuel Langhorne Clemens noted English has too many exceptions. | |
| 123 | small = c( | |
| 124 | "one", "two", "three", "four", "five", | |
| 125 | "six", "seven", "eight", "nine", "ten", | |
| 126 | "eleven", "twelve", "thirteen", "fourteen", "fifteen", | |
| 127 | "sixteen", "seventeen", "eighteen", "nineteen" | |
| 128 | ) | |
| 129 | ||
| 130 | ord_small = c( | |
| 131 | "first", "second", "third", "fourth", "fifth", | |
| 132 | "sixth", "seventh", "eighth", "ninth", "tenth", | |
| 133 | "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", | |
| 134 | "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth" | |
| 135 | ) | |
| 136 | ||
| 137 | # After this, the number (n) is between 20 and 99. | |
| 138 | if( n < 20 ) { | |
| 139 | if( ordinal ) { | |
| 140 | return( .subset( ord_small, n %% 100 ) ) | |
| 141 | } | |
| 142 | ||
| 143 | return( .subset( small, n %% 100 ) ) | |
| 144 | } | |
| 145 | ||
| 146 | tens = c( "", | |
| 147 | "twenty", "thirty", "forty", "fifty", | |
| 148 | "sixty", "seventy", "eighty", "ninety" | |
| 149 | ) | |
| 150 | ||
| 151 | ord_tens = c( "", | |
| 152 | "twentieth", "thirtieth", "fortieth", "fiftieth", | |
| 153 | "sixtieth", "seventieth", "eightieth", "ninetieth" | |
| 154 | ) | |
| 155 | ||
| 156 | ones_index = n %% 10 | |
| 157 | n = n %/% 10 | |
| 158 | ||
| 159 | # No number in the ones column, so the number must be a multiple of ten. | |
| 160 | if( ones_index == 0 ) { | |
| 161 | if( ordinal ) { | |
| 162 | return( .subset( ord_tens, n ) ) | |
| 163 | } | |
| 164 | ||
| 165 | return( .subset( tens, n ) ) | |
| 166 | } | |
| 167 | ||
| 168 | # Find the value from the ones column. | |
| 169 | if( ordinal ) { | |
| 170 | unit_1 = .subset( ord_small, ones_index ) | |
| 171 | } | |
| 172 | else { | |
| 173 | unit_1 = .subset( small, ones_index ) | |
| 174 | } | |
| 175 | ||
| 176 | # Find the tens column. | |
| 177 | unit_10 = .subset( tens, n ) | |
| 178 | ||
| 179 | # Hyphenate the tens and the ones together. | |
| 180 | concat( unit_10, concat( "-", unit_1 ) ) | |
| 181 | } | |
| 182 | ||
| 183 | # ----------------------------------------------------------------------------- | |
| 184 | # Returns a number as a comma-delimited string. This is a work-around | |
| 185 | # until Renjin fixes https://github.com/bedatadriven/renjin/issues/338 | |
| 186 | # ----------------------------------------------------------------------------- | |
| 187 | commas <- function( n ) { | |
| 188 | n <- x( n ) | |
| 189 | ||
| 190 | s <- sprintf( "%03.0f", n %% 1000 ) | |
| 191 | n <- n %/% 1000 | |
| 192 | ||
| 193 | while( n > 0 ) { | |
| 194 | s <- concat( sprintf( "%03.0f", n %% 1000 ), ',', s ) | |
| 195 | n <- n %/% 1000 | |
| 196 | } | |
| 197 | ||
| 198 | gsub( '^0*', '', s ) | |
| 199 | } | |
| 200 | ||
| 201 | # ----------------------------------------------------------------------------- | |
| 202 | # Returns a human-readable string that provides the elapsed time between | |
| 203 | # two numbers in terms of years, months, and days. If any unit value is zero, | |
| 204 | # the unit is not included. The words (year, month, day) are pluralized | |
| 205 | # according to English grammar. The numbers are written out according to | |
| 206 | # Chicago Manual of Style. This applies the serial comma. | |
| 207 | # | |
| 208 | # Both numbers are offsets relative to the anchor date. | |
| 209 | # | |
| 210 | # If all unit values are zero, this returns s ("same day" by default). | |
| 211 | # | |
| 212 | # If the start date (began) is greater than end date (ended), the dates are | |
| 213 | # swapped before calculations are performed. This allows any two dates | |
| 214 | # to be compared and positive unit values are always returned. | |
| 215 | # ----------------------------------------------------------------------------- | |
| 216 | elapsed <- function( began, ended, s = "same day" ) { | |
| 217 | began = when( anchor, began ) | |
| 218 | ended = when( anchor, ended ) | |
| 219 | ||
| 220 | # Swap the dates if the end date comes before the start date. | |
| 221 | if( as.integer( ended - began ) < 0 ) { | |
| 222 | tempd = began | |
| 223 | began = ended | |
| 224 | ended = tempd | |
| 225 | } | |
| 226 | ||
| 227 | # Calculate number of elapsed years. | |
| 228 | years = length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 229 | ||
| 230 | # Move the start date up by the number of elapsed years. | |
| 231 | if( years > 0 ) { | |
| 232 | began = seq( began, length = 2, by = concat( years, " years" ) )[2] | |
| 233 | years = pl.numeric( "year", years ) | |
| 234 | } | |
| 235 | else { | |
| 236 | # Zero years. | |
| 237 | years = "" | |
| 238 | } | |
| 239 | ||
| 240 | # Calculate number of elapsed months, excluding years. | |
| 241 | months = length( seq( from = began, to = ended, by = 'month' ) ) - 1 | |
| 242 | ||
| 243 | # Move the start date up by the number of elapsed months | |
| 244 | if( months > 0 ) { | |
| 245 | began = seq( began, length = 2, by = concat( months, " months" ) )[2] | |
| 246 | months = pl.numeric( "month", months ) | |
| 247 | } | |
| 248 | else { | |
| 249 | # Zero months | |
| 250 | months = "" | |
| 251 | } | |
| 252 | ||
| 253 | # Calculate number of elapsed days, excluding months and years. | |
| 254 | days = length( seq( from = began, to = ended, by = 'day' ) ) - 1 | |
| 255 | ||
| 256 | if( days > 0 ) { | |
| 257 | days = pl.numeric( "day", days ) | |
| 258 | } | |
| 259 | else { | |
| 260 | # Zero days | |
| 261 | days = "" | |
| 262 | } | |
| 263 | ||
| 264 | if( years <= 0 && months <= 0 && days <= 0 ) { | |
| 265 | return( s ) | |
| 266 | } | |
| 267 | ||
| 268 | # Put them all in a vector, then remove the empty values. | |
| 269 | s <- c( years, months, days ) | |
| 270 | s <- s[ s != "" ] | |
| 271 | ||
| 272 | r <- paste( s, collapse = ", " ) | |
| 273 | ||
| 274 | # If all three items are present, replace the last comma with ", and". | |
| 275 | if( length( s ) > 2 ) { | |
| 276 | return( gsub( "(.*),", "\\1, and", r ) ) | |
| 277 | } | |
| 278 | ||
| 279 | # Does nothing if no commas are present. | |
| 280 | gsub( "(.*),", "\\1 and", r ) | |
| 281 | } | |
| 282 | ||
| 283 | # ----------------------------------------------------------------------------- | |
| 284 | # Returns the number (n) in English followed by the plural or singular | |
| 285 | # form of the given string (s; resumably a noun), if applicable, according | |
| 286 | # to English grammar. That is, pl.numeric( "wolf", 5 ) will return | |
| 287 | # "five wolves". | |
| 288 | # ----------------------------------------------------------------------------- | |
| 289 | pl.numeric <- function( s, n ) { | |
| 290 | concat( cms( n ), concat( " ", pluralize( s, n ) ) ) | |
| 291 | } | |
| 292 | ||
| 293 | # ----------------------------------------------------------------------------- | |
| 294 | # Pluralize s if n is not equal to 1. | |
| 295 | # ----------------------------------------------------------------------------- | |
| 296 | pl <- function( s, n=2 ) { | |
| 297 | pluralize( s, x( n ) ) | |
| 298 | } | |
| 299 | ||
| 300 | # ----------------------------------------------------------------------------- | |
| 301 | # Name of the season, starting with an capital letter. | |
| 302 | # ----------------------------------------------------------------------------- | |
| 303 | season <- function( n, format = "%Y-%m-%d" ) { | |
| 304 | WS <- as.Date("2016-12-15", "%Y-%m-%d") # Winter Solstice | |
| 305 | SE <- as.Date("2016-03-15", "%Y-%m-%d") # Spring Equinox | |
| 306 | SS <- as.Date("2016-06-15", "%Y-%m-%d") # Summer Solstice | |
| 307 | AE <- as.Date("2016-09-15", "%Y-%m-%d") # Autumn Equinox | |
| 308 | ||
| 309 | d <- when( anchor, n ) | |
| 310 | d <- as.Date( strftime( d, format="2016-%m-%d" ) ) | |
| 311 | ||
| 312 | ifelse( d >= WS | d < SE, "Winter", | |
| 313 | ifelse( d >= SE & d < SS, "Spring", | |
| 314 | ifelse( d >= SS & d < AE, "Summer", "Autumn" ) | |
| 315 | ) | |
| 316 | ) | |
| 317 | } | |
| 318 | ||
| 319 | # ----------------------------------------------------------------------------- | |
| 320 | # Converts the first letter in a string to lowercase | |
| 321 | # ----------------------------------------------------------------------------- | |
| 322 | lc <- function( s ) { | |
| 323 | concat( tolower( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 324 | } | |
| 325 | ||
| 326 | # ----------------------------------------------------------------------------- | |
| 327 | # Converts the entire string to lowercase | |
| 328 | # ----------------------------------------------------------------------------- | |
| 329 | lower <- tolower | |
| 330 | ||
| 331 | # ----------------------------------------------------------------------------- | |
| 332 | # Converts the first letter in a string to uppercase | |
| 333 | # ----------------------------------------------------------------------------- | |
| 334 | uc <- function( s ) { | |
| 335 | concat( toupper( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 336 | } | |
| 337 | ||
| 338 | # ----------------------------------------------------------------------------- | |
| 339 | # Returns the number of days between the given dates. | |
| 340 | # ----------------------------------------------------------------------------- | |
| 341 | days <- function( d1, d2, format = "%Y-%m-%d" ) { | |
| 342 | dates = c( d1, d2 ) | |
| 343 | dt = strptime( dates, format = format ) | |
| 344 | as.integer( difftime( dates[2], dates[1], units = "days" ) ) | |
| 345 | } | |
| 346 | ||
| 347 | # ----------------------------------------------------------------------------- | |
| 348 | # Returns the number of years elapsed. | |
| 349 | # ----------------------------------------------------------------------------- | |
| 350 | years <- function( began, ended ) { | |
| 351 | began = when( anchor, began ) | |
| 352 | ended = when( anchor, ended ) | |
| 353 | ||
| 354 | # Swap the dates if the end date comes before the start date. | |
| 355 | if( as.integer( ended - began ) < 0 ) { | |
| 356 | tempd = began | |
| 357 | began = ended | |
| 358 | ended = tempd | |
| 359 | } | |
| 360 | ||
| 361 | # Calculate number of elapsed years. | |
| 362 | length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 363 | } | |
| 364 | ||
| 365 | # ----------------------------------------------------------------------------- | |
| 366 | # Full name of the month, starting with a capital letter. | |
| 367 | # ----------------------------------------------------------------------------- | |
| 368 | month <- function( n ) { | |
| 369 | # Faster than month.name[ x( n ) ] | |
| 370 | .subset( month.name, x( n ) ) | |
| 371 | } | |
| 372 | ||
| 373 | # ----------------------------------------------------------------------------- | |
| 374 | # ----------------------------------------------------------------------------- | |
| 375 | money <- function( n ) { | |
| 376 | commas( x( n ) ) | |
| 377 | } | |
| 378 | ||
| 379 | # ----------------------------------------------------------------------------- | |
| 380 | # ----------------------------------------------------------------------------- | |
| 381 | timeline <- function( n ) { | |
| 382 | concat( weekday( n ), ", ", annal( n ), " (", season( n ), ")" ) | |
| 383 | } | |
| 384 | ||
| 385 | # ----------------------------------------------------------------------------- | |
| 386 | # Rounds to the nearest base value (e.g., round to nearest 10). | |
| 387 | # | |
| 388 | # @param base The nearest value to round to. | |
| 389 | # ----------------------------------------------------------------------------- | |
| 390 | round.up <- function( n, base = 5 ) { | |
| 391 | base * round( x( n ) / base ) | |
| 392 | } | |
| 393 | ||
| 394 | # ----------------------------------------------------------------------------- | |
| 395 | # Computes linear distance between two points using Haversine formula. | |
| 396 | # Although Earth is an oblate spheroid, this will produce results close | |
| 397 | # enough for most purposes. | |
| 398 | # | |
| 399 | # @param lat1/lon1 The source latitude and longitude. | |
| 400 | # @param lat2/lon2 The destination latitude and longitude. | |
| 401 | # @param radius The radius of the sphere. | |
| 402 | # | |
| 403 | # @return The distance between the two coordinates in meters. | |
| 404 | # ----------------------------------------------------------------------------- | |
| 405 | haversine <- function( lat1, lon1, lat2, lon2, radius = 6371 ) { | |
| 406 | # Convert decimal degrees to radians | |
| 407 | lon1 = lon1 * pi / 180 | |
| 408 | lon2 = lon2 * pi / 180 | |
| 409 | lat1 = lat1 * pi / 180 | |
| 410 | lat2 = lat2 * pi / 180 | |
| 411 | ||
| 412 | # Haversine formula | |
| 413 | dlon = lon2 - lon1 | |
| 414 | dlat = lat2 - lat1 | |
| 415 | a = sin( dlat / 2 ) ** 2 + cos( lat1 ) * cos( lat2 ) * sin( dlon / 2 ) ** 2 | |
| 416 | c = 2 * atan2( sqrt( a ), sqrt( 1-a ) ) | |
| 417 | ||
| 418 | return( radius * c * 1000 ) | |
| 419 | } | |
| 420 | ||
| 1 | 421 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Converts CSV to Markdown. | |
| 26 | # | |
| 27 | # Reads a CSV file and converts the contents to a Markdown table. The | |
| 28 | # file must be in the working directory as specified by setwd. | |
| 29 | # | |
| 30 | # @param f The filename to convert. | |
| 31 | # @param decimals Rounded decimal places (default 1). | |
| 32 | # @param totals Include total sums (default TRUE). | |
| 33 | # @param align Right-align numbers (default TRUE). | |
| 34 | # ----------------------------------------------------------------------------- | |
| 35 | csv2md <- function( f, decimals = 2, totals = T, align = T ) { | |
| 36 | # Read the CVS data from the file; ensure strings become characters. | |
| 37 | df <- read.table( f, sep=',', header=T, stringsAsFactors=F ) | |
| 38 | ||
| 39 | if( totals ) { | |
| 40 | # Determine what columns can be summed. | |
| 41 | number <- which( unlist( lapply( df, is.numeric ) ) ) | |
| 42 | ||
| 43 | # Use colSums when more than one summable column exists. | |
| 44 | if( length( number ) > 1 ) { | |
| 45 | f.sum <- colSums | |
| 46 | } | |
| 47 | else { | |
| 48 | f.sum <- sum | |
| 49 | } | |
| 50 | ||
| 51 | # Calculate the sum of all the summable columns and insert the | |
| 52 | # results back into the data frame. | |
| 53 | df[ (nrow( df ) + 1), number ] <- f.sum( df[, number], na.rm=TRUE ) | |
| 54 | ||
| 55 | # pluralise would be heavyweight here. | |
| 56 | if( length( number ) > 1 ) { | |
| 57 | t <- "**Totals**" | |
| 58 | } | |
| 59 | else { | |
| 60 | t <- "**Total**" | |
| 61 | } | |
| 62 | ||
| 63 | # Change the first column of the last line to "Total(s)". | |
| 64 | df[ nrow( df ), 1 ] <- t | |
| 65 | ||
| 66 | # Don't clutter the output with "NA" text. | |
| 67 | df[ is.na( df ) ] <- "" | |
| 68 | } | |
| 69 | ||
| 70 | if( align ) { | |
| 71 | is.char <- vapply( df, is.character, logical( 1 ) ) | |
| 72 | dashes <- paste( ifelse( is.char, ':---', '---:' ), collapse='|' ) | |
| 73 | } | |
| 74 | else { | |
| 75 | dashes <- paste( rep( '---', length( df ) ), collapse = '|' ) | |
| 76 | } | |
| 77 | ||
| 78 | # Create a Markdown version of the data frame. | |
| 79 | paste( | |
| 80 | paste( names( df ), collapse = '|'), '\n', | |
| 81 | dashes, '\n', | |
| 82 | paste( | |
| 83 | Reduce( function( x, y ) { | |
| 84 | paste( x, format( y, digits = decimals ), sep = '|' ) | |
| 85 | }, df | |
| 86 | ), | |
| 87 | collapse = '|\n', sep='' | |
| 88 | ) | |
| 89 | ) | |
| 90 | } | |
| 91 | ||
| 1 | 92 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # See Damian Conway's "An Algorithmic Approach to English Pluralization": | |
| 26 | # http://goo.gl/oRL4MP | |
| 27 | # See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/ | |
| 28 | # See Shevek's Pluralizer: https://github.com/shevek/linguistics/ | |
| 29 | # See also: http://www.freevectors.net/assets/files/plural.txt | |
| 30 | # ----------------------------------------------------------------------------- | |
| 31 | pluralize <- function( s, n ) { | |
| 32 | result <- s | |
| 33 | ||
| 34 | # Partial implementation of Conway's algorithm for nouns. | |
| 35 | if( n != 1 ) { | |
| 36 | if( pl.noninflective( s ) || | |
| 37 | pl.suffix( "es", s ) || | |
| 38 | pl.suffix( "fish", s ) || | |
| 39 | pl.suffix( "ois", s ) || | |
| 40 | pl.suffix( "sheep", s ) || | |
| 41 | pl.suffix( "deer", s ) || | |
| 42 | pl.suffix( "pox", s ) || | |
| 43 | pl.suffix( "[A-Z].*ese", s ) || | |
| 44 | pl.suffix( "itis", s ) ) { | |
| 45 | # 1. Retain non-inflective user-mapped noun as is. | |
| 46 | # 2. Retain non-inflective plural as is. | |
| 47 | result <- s | |
| 48 | } | |
| 49 | else if( pl.is.irregular.pl( s ) ) { | |
| 50 | # 4. Change irregular plurals based on mapping. | |
| 51 | result <- pl.irregular.pl( s ) | |
| 52 | } | |
| 53 | else if( pl.is.irregular.es( s ) ) { | |
| 54 | # x. From Shevek's | |
| 55 | result <- pl.inflect( s, "", "es" ) | |
| 56 | } | |
| 57 | else if( pl.suffix( "man", s ) ) { | |
| 58 | # 5. For -man, change -an to -en | |
| 59 | result <- pl.inflect( s, "an", "en" ) | |
| 60 | } | |
| 61 | else if( pl.suffix( "[lm]ouse", s ) ) { | |
| 62 | # 5. For [lm]ouse, change -ouse to -ice | |
| 63 | result <- pl.inflect( s, "ouse", "ice" ) | |
| 64 | } | |
| 65 | else if( pl.suffix( "tooth", s ) ) { | |
| 66 | # 5. For -tooth, change -ooth to -eeth | |
| 67 | result <- pl.inflect( s, "ooth", "eeth" ) | |
| 68 | } | |
| 69 | else if( pl.suffix( "goose", s ) ) { | |
| 70 | # 5. For -goose, change -oose to -eese | |
| 71 | result <- pl.inflect( s, "oose", "eese" ) | |
| 72 | } | |
| 73 | else if( pl.suffix( "foot", s ) ) { | |
| 74 | # 5. For -foot, change -oot to -eet | |
| 75 | result <- pl.inflect( s, "oot", "eet" ) | |
| 76 | } | |
| 77 | else if( pl.suffix( "zoon", s ) ) { | |
| 78 | # 5. For -zoon, change -on to -a | |
| 79 | result <- pl.inflect( s, "on", "a" ) | |
| 80 | } | |
| 81 | else if( pl.suffix( "[csx]is", s ) ) { | |
| 82 | # 5. Change -cis, -sis, -xis to -es | |
| 83 | result <- pl.inflect( s, "is", "es" ) | |
| 84 | } | |
| 85 | else if( pl.suffix( "([cs]h|ss|zz|x|s)", s ) ) { | |
| 86 | # 8. Change -ch, -sh, -ss, -zz, -x, -s to -es | |
| 87 | result <- pl.inflect( s, "", "es" ) | |
| 88 | } | |
| 89 | else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) { | |
| 90 | # 9. Change -f to -ves | |
| 91 | result <- pl.inflect( s, "f", "ves" ) | |
| 92 | } | |
| 93 | else if( pl.suffix( "[nlw]ife", s ) ) { | |
| 94 | # 10. Change -fe to -ves | |
| 95 | result <- pl.inflect( s, "fe", "ves" ) | |
| 96 | } | |
| 97 | else if( pl.suffix( "[aeiou]y", s ) ) { | |
| 98 | # 11. Change -[aeiou]y to -ys | |
| 99 | result <- pl.inflect( s, "", "s" ) | |
| 100 | } | |
| 101 | else if( pl.suffix( "y", s ) ) { | |
| 102 | # 12. Change -y to -ies | |
| 103 | result <- pl.inflect( s, "y", "ies" ) | |
| 104 | } | |
| 105 | else if( pl.suffix( "z", s ) ) { | |
| 106 | # x. Change -z to -zzes | |
| 107 | result <- pl.inflect( s, "", "zes" ) | |
| 108 | } | |
| 109 | else { | |
| 110 | # 13. Default plural: add -s | |
| 111 | result <- pl.inflect( s, "", "s" ) | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | result | |
| 116 | } | |
| 117 | ||
| 118 | # ----------------------------------------------------------------------------- | |
| 119 | # Returns the given string (s) with its suffix replaced by r. | |
| 120 | # ----------------------------------------------------------------------------- | |
| 121 | pl.inflect <- function( s, suffix, r ) { | |
| 122 | gsub( paste( suffix, "$", sep="" ), r, s ) | |
| 123 | } | |
| 124 | ||
| 125 | # ----------------------------------------------------------------------------- | |
| 126 | # Answers whether the given string (s) has the given ending. | |
| 127 | # ----------------------------------------------------------------------------- | |
| 128 | pl.suffix <- function( ending, s ) { | |
| 129 | grepl( paste( ending, "$", sep="" ), s ) | |
| 130 | } | |
| 131 | ||
| 132 | # ----------------------------------------------------------------------------- | |
| 133 | # Answers whether the given string (s) is a noninflective noun. | |
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | pl.noninflective <- function( s ) { | |
| 136 | v <- c( | |
| 137 | "aircraft", | |
| 138 | "Bhutanese", | |
| 139 | "bison", | |
| 140 | "bream", | |
| 141 | "Burmese", | |
| 142 | "carp", | |
| 143 | "chassis", | |
| 144 | "Chinese", | |
| 145 | "clippers", | |
| 146 | "cod", | |
| 147 | "contretemps", | |
| 148 | "corps", | |
| 149 | "debris", | |
| 150 | "djinn", | |
| 151 | "eland", | |
| 152 | "elk", | |
| 153 | "flounder", | |
| 154 | "fracas", | |
| 155 | "gallows", | |
| 156 | "graffiti", | |
| 157 | "headquarters", | |
| 158 | "high-jinks", | |
| 159 | "homework", | |
| 160 | "hovercraft", | |
| 161 | "innings", | |
| 162 | "Japanese", | |
| 163 | "Lebanese", | |
| 164 | "mackerel", | |
| 165 | "means", | |
| 166 | "mews", | |
| 167 | "mice", | |
| 168 | "mumps", | |
| 169 | "news", | |
| 170 | "pincers", | |
| 171 | "pliers", | |
| 172 | "Portuguese", | |
| 173 | "proceedings", | |
| 174 | "salmon", | |
| 175 | "scissors", | |
| 176 | "sea-bass", | |
| 177 | "Senegalese", | |
| 178 | "shears", | |
| 179 | "Siamese", | |
| 180 | "Sinhalese", | |
| 181 | "spacecraft", | |
| 182 | "swine", | |
| 183 | "trout", | |
| 184 | "tuna", | |
| 185 | "Vietnamese", | |
| 186 | "watercraft", | |
| 187 | "whiting", | |
| 188 | "wildebeest" | |
| 189 | ) | |
| 190 | ||
| 191 | is.element( s, v ) | |
| 192 | } | |
| 193 | ||
| 194 | # ----------------------------------------------------------------------------- | |
| 195 | # Answers whether the given string (s) is an irregular plural. | |
| 196 | # ----------------------------------------------------------------------------- | |
| 197 | pl.is.irregular.pl <- function( s ) { | |
| 198 | # Could be refactored with pl.irregular.pl... | |
| 199 | v <- c( | |
| 200 | "beef", "brother", "child", "cow", "ephemeris", "genie", "money", | |
| 201 | "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby" | |
| 202 | ) | |
| 203 | ||
| 204 | is.element( s, v ) | |
| 205 | } | |
| 206 | ||
| 207 | # ----------------------------------------------------------------------------- | |
| 208 | # Call to pluralize an irregular noun. Only call after confirming | |
| 209 | # the noun is irregular via pl.is.irregular.pl. | |
| 210 | # ----------------------------------------------------------------------------- | |
| 211 | pl.irregular.pl <- function( s ) { | |
| 212 | v <- list( | |
| 213 | "beef" = "beefs", | |
| 214 | "brother" = "brothers", | |
| 215 | "child" = "children", | |
| 216 | "cow" = "cows", | |
| 217 | "ephemeris" = "ephemerides", | |
| 218 | "genie" = "genies", | |
| 219 | "money" = "moneys", | |
| 220 | "mongoose" = "mongooses", | |
| 221 | "mythos" = "mythoi", | |
| 222 | "octopus" = "octopuses", | |
| 223 | "ox" = "oxen", | |
| 224 | "soliloquy" = "soliloquies", | |
| 225 | "trilby" = "trilbys" | |
| 226 | ) | |
| 227 | ||
| 228 | # Faster version of v[[ s ]] | |
| 229 | .subset2( v, s ) | |
| 230 | } | |
| 231 | ||
| 232 | # ----------------------------------------------------------------------------- | |
| 233 | # Answers whether the given string (s) pluralizes with -es. | |
| 234 | # ----------------------------------------------------------------------------- | |
| 235 | pl.is.irregular.es <- function( s ) { | |
| 236 | v <- c( | |
| 237 | "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis", | |
| 238 | "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais", | |
| 239 | "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris", | |
| 240 | "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis", | |
| 241 | "polis", "rhinoceros", "sassafrass", "trellis" | |
| 242 | ) | |
| 243 | ||
| 244 | is.element( s, v ) | |
| 245 | } | |
| 246 | ||
| 1 | 247 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Returns leftmost n characters of s. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | lstr <- function( s, n = 1 ) { | |
| 28 | substr( s, 0, n ) | |
| 29 | } | |
| 30 | ||
| 31 | # ----------------------------------------------------------------------------- | |
| 32 | # Returns rightmost n characters of s. | |
| 33 | # ----------------------------------------------------------------------------- | |
| 34 | rstr <- function( s, n = 1 ) { | |
| 35 | l <- nchar( s ) | |
| 36 | substr( s, l - n + 1, l ) | |
| 37 | } | |
| 38 | ||
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | # Returns the possessive form of the given word, s. | |
| 41 | # ----------------------------------------------------------------------------- | |
| 42 | pos <- function( s ) { | |
| 43 | lcs <- tolower( s ) | |
| 44 | pronouns <- c( 'your', 'our', 'her', 'it', 'their' ) | |
| 45 | ||
| 46 | if( lcs == 'my' ) { | |
| 47 | # Change "[Mm]y" to "[Mm]ine". | |
| 48 | s <- paste0( lstr( s, 1 ), "ine" ) | |
| 49 | } | |
| 50 | else if( lcs %in% pronouns ) { | |
| 51 | # Append an s to most pronouns. | |
| 52 | s <- paste0( s, 's' ) | |
| 53 | } | |
| 54 | else if( lcs != 'his' ) { | |
| 55 | # Possessive for all other words except 'his'. | |
| 56 | s <- paste0( s, ifelse( rstr( s, 1 ) == 's', "'" ,"'s" ) ) | |
| 57 | } | |
| 58 | ||
| 59 | s | |
| 60 | } | |
| 61 | ||
| 1 | 62 |
| 1 | #  Scrivenvar | |
| 2 | ||
| 3 | A text editor that uses [interpolated strings](https://en.wikipedia.org/wiki/String_interpolation) to reference externally defined values. | |
| 4 | ||
| 5 | ## Download | |
| 6 | ||
| 7 | Download one of the following editions: | |
| 8 | ||
| 9 | * [Windows](https://gitreleases.dev/gh/DaveJarvis/scrivenvar/latest/scrivenvar.exe) | |
| 10 | * [Linux](https://gitreleases.dev/gh/DaveJarvis/scrivenvar/latest/scrivenvar.bin) | |
| 11 | * [Java Archive](https://gitreleases.dev/gh/DaveJarvis/scrivenvar/latest/scrivenvar.jar) | |
| 12 | ||
| 13 | ## Run | |
| 14 | ||
| 15 | Note that the first time the application runs, it will unpack itself into a local directory. Subsequent starts will be faster. | |
| 16 | ||
| 17 | ### Windows | |
| 18 | ||
| 19 | On Windows, double-click the application to start. You will have to give the application permission to run. | |
| 20 | ||
| 21 | When upgrading to a new version, delete the following directory; | |
| 22 | ||
| 23 | C:\Users\%USERNAME%\AppData\Local\warp\packages\scrivenvar.exe | |
| 24 | ||
| 25 | ### Linux | |
| 26 | ||
| 27 | On Linux, run `chmod +x scrivenvar.bin` then `./scrivenvar.bin`. | |
| 28 | ||
| 29 | ### Other | |
| 30 | ||
| 31 | On other platforms, download and install a full version of [OpenJDK 14](https://bell-sw.com/) that includes JavaFX module support, then run: | |
| 32 | ||
| 33 | ``` bash | |
| 34 | java -jar scrivenvar.jar | |
| 35 | ``` | |
| 36 | ||
| 37 | ## Features | |
| 38 | ||
| 39 | * User-defined interpolated strings | |
| 40 | * Real-time preview with variable substitution | |
| 41 | * Auto-complete variable names based on variable values | |
| 42 | * XML document transformation using XSLT3 or older | |
| 43 | * Platform independent (Windows, Linux, MacOS) | |
| 44 | * Spellcheck while typing | |
| 45 | * Write mathematical formulas using a subset of TeX | |
| 46 | * R integration | |
| 47 | ||
| 48 | ## Usage | |
| 49 | ||
| 50 | See the [detailed documentation](docs/README.md) for information about | |
| 51 | using the application. | |
| 52 | ||
| 53 | ## Screenshots | |
| 54 | ||
| 55 |  | |
| 56 | ||
| 57 |  | |
| 58 | ||
| 59 | ## License | |
| 60 | ||
| 61 | This software is licensed under the [BSD 2-Clause License](LICENSE.md) and | |
| 62 | derived heavily from [Markdown-Writer-FX](licenses/MARKDOWN-WRITER-FX.md). | |
| 63 | ||
| 1 | 64 |
| 1 | --- | |
| 2 | application: | |
| 3 | title: "Scrivenvar" | |
| 1 | 4 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | # ----------------------------------------------------------------------------- | |
| 4 | # Copyright 2020 Dave Jarvis | |
| 5 | # | |
| 6 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 7 | # copy of this software and associated documentation files (the | |
| 8 | # "Software"), to deal in the Software without restriction, including | |
| 9 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 10 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 11 | # permit persons to whom the Software is furnished to do so, subject to | |
| 12 | # the following conditions: | |
| 13 | # | |
| 14 | # The above copyright notice and this permission notice shall be included | |
| 15 | # in all copies or substantial portions of the Software. | |
| 16 | # | |
| 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 18 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 19 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 20 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 21 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 22 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 23 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | ||
| 26 | set -o errexit | |
| 27 | set -o nounset | |
| 28 | ||
| 29 | readonly SCRIPT_SRC="$(dirname "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}")" | |
| 30 | readonly SCRIPT_DIR="$(cd "${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)" | |
| 31 | readonly SCRIPT_NAME=$(basename "$0") | |
| 32 | ||
| 33 | # ----------------------------------------------------------------------------- | |
| 34 | # The main entry point is responsible for parsing command-line arguments, | |
| 35 | # changing to the appropriate directory, and running all commands requested | |
| 36 | # by the user. | |
| 37 | # | |
| 38 | # $@ - Command-line arguments | |
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | main() { | |
| 41 | arguments "$@" | |
| 42 | ||
| 43 | $usage && terminate 3 | |
| 44 | requirements && terminate 4 | |
| 45 | traps && terminate 5 | |
| 46 | ||
| 47 | directory && terminate 6 | |
| 48 | preprocess && terminate 7 | |
| 49 | execute && terminate 8 | |
| 50 | postprocess && terminate 9 | |
| 51 | ||
| 52 | terminate 0 | |
| 53 | } | |
| 54 | ||
| 55 | # ----------------------------------------------------------------------------- | |
| 56 | # Perform all commands that the script requires. | |
| 57 | # | |
| 58 | # @return 0 - Indicate to terminate the script with non-zero exit level | |
| 59 | # @return 1 - All tasks completed successfully (default) | |
| 60 | # ----------------------------------------------------------------------------- | |
| 61 | execute() { | |
| 62 | return 1 | |
| 63 | } | |
| 64 | ||
| 65 | # ----------------------------------------------------------------------------- | |
| 66 | # Changes to the script's working directory, provided it exists. | |
| 67 | # | |
| 68 | # @return 0 - Change directory failed | |
| 69 | # @return 1 - Change directory succeeded | |
| 70 | # ----------------------------------------------------------------------------- | |
| 71 | directory() { | |
| 72 | $log "Change directory" | |
| 73 | local result=1 | |
| 74 | ||
| 75 | # Track whether change directory failed. | |
| 76 | cd "${SCRIPT_DIR}" > /dev/null 2>&1 || result=0 | |
| 77 | ||
| 78 | return "${result}" | |
| 79 | } | |
| 80 | ||
| 81 | # ----------------------------------------------------------------------------- | |
| 82 | # Perform any initialization required prior to executing tasks. | |
| 83 | # | |
| 84 | # @return 0 - Preprocessing failed | |
| 85 | # @return 1 - Preprocessing succeeded | |
| 86 | # ----------------------------------------------------------------------------- | |
| 87 | preprocess() { | |
| 88 | $log "Preprocess" | |
| 89 | ||
| 90 | return 1 | |
| 91 | } | |
| 92 | ||
| 93 | # ----------------------------------------------------------------------------- | |
| 94 | # Perform any clean up required prior to executing tasks. | |
| 95 | # | |
| 96 | # @return 0 - Postprocessing failed | |
| 97 | # @return 1 - Postprocessing succeeded | |
| 98 | # ----------------------------------------------------------------------------- | |
| 99 | postprocess() { | |
| 100 | $log "Postprocess" | |
| 101 | ||
| 102 | return 1 | |
| 103 | } | |
| 104 | ||
| 105 | # ----------------------------------------------------------------------------- | |
| 106 | # Check that all required commands are available. | |
| 107 | # | |
| 108 | # @return 0 - At least one command is missing | |
| 109 | # @return 1 - All commands are available | |
| 110 | # ----------------------------------------------------------------------------- | |
| 111 | requirements() { | |
| 112 | $log "Verify requirements" | |
| 113 | local -r expected_count=${#DEPENDENCIES[@]} | |
| 114 | local total_count=0 | |
| 115 | ||
| 116 | # Verify that each command exists. | |
| 117 | for dependency in "${DEPENDENCIES[@]}"; do | |
| 118 | # Extract the command name [0] and URL [1]. | |
| 119 | IFS=',' read -ra dependent <<< "${dependency}" | |
| 120 | ||
| 121 | required "${dependent[0]}" "${dependent[1]}" | |
| 122 | total_count=$(( total_count + $? )) | |
| 123 | done | |
| 124 | ||
| 125 | unset IFS | |
| 126 | ||
| 127 | # Total dependencies found must match the expected number. | |
| 128 | # Integer-only division rounds down. | |
| 129 | return $(( total_count / expected_count )) | |
| 130 | } | |
| 131 | ||
| 132 | # ----------------------------------------------------------------------------- | |
| 133 | # Called before terminating the script. | |
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | cleanup() { | |
| 136 | $log "Cleanup" | |
| 137 | } | |
| 138 | ||
| 139 | # ----------------------------------------------------------------------------- | |
| 140 | # Terminates the program immediately. | |
| 141 | # ----------------------------------------------------------------------------- | |
| 142 | trap_control_c() { | |
| 143 | $log "Interrupted" | |
| 144 | cleanup | |
| 145 | error "⯃" | |
| 146 | terminate 1 | |
| 147 | } | |
| 148 | ||
| 149 | # ----------------------------------------------------------------------------- | |
| 150 | # Configure signal traps. | |
| 151 | # | |
| 152 | # @return 1 - Signal traps are set. | |
| 153 | # ----------------------------------------------------------------------------- | |
| 154 | traps() { | |
| 155 | # Suppress echoing ^C if pressed. | |
| 156 | stty -echoctl | |
| 157 | trap trap_control_c INT | |
| 158 | ||
| 159 | return 1 | |
| 160 | } | |
| 161 | ||
| 162 | # ----------------------------------------------------------------------------- | |
| 163 | # Check for a required command. | |
| 164 | # | |
| 165 | # $1 - Command or file to check for existence | |
| 166 | # $2 - Command's website (e.g., download for binaries and source code) | |
| 167 | # | |
| 168 | # @return 0 - Command is missing | |
| 169 | # @return 1 - Command exists | |
| 170 | # ----------------------------------------------------------------------------- | |
| 171 | required() { | |
| 172 | local result=0 | |
| 173 | ||
| 174 | test -f "$1" || \ | |
| 175 | command -v "$1" > /dev/null 2>&1 && result=1 || \ | |
| 176 | warning "Missing: $1 ($2)" | |
| 177 | ||
| 178 | return ${result} | |
| 179 | } | |
| 180 | ||
| 181 | # ----------------------------------------------------------------------------- | |
| 182 | # Show acceptable command-line arguments. | |
| 183 | # | |
| 184 | # @return 0 - Indicate script may not continue | |
| 185 | # ----------------------------------------------------------------------------- | |
| 186 | utile_usage() { | |
| 187 | printf "Usage: %s [OPTIONS...]\n\n" "${SCRIPT_NAME}" >&2 | |
| 188 | ||
| 189 | # Number of spaces to pad after the longest long argument. | |
| 190 | local -r PADDING=2 | |
| 191 | ||
| 192 | # Determine the longest long argument to adjust spacing. | |
| 193 | local -r LEN=$(printf '%s\n' "${ARGUMENTS[@]}" | \ | |
| 194 | awk -F"," '{print length($2)+'${PADDING}'}' | sort -n | tail -1) | |
| 195 | ||
| 196 | local duplicates | |
| 197 | ||
| 198 | for argument in "${ARGUMENTS[@]}"; do | |
| 199 | # Extract the short [0] and long [1] arguments and description [2]. | |
| 200 | arg=("$(echo ${argument} | cut -d ',' -f1)" \ | |
| 201 | "$(echo ${argument} | cut -d ',' -f2)" \ | |
| 202 | "$(echo ${argument} | cut -d ',' -f3-)") | |
| 203 | ||
| 204 | duplicates+=("${arg[0]}") | |
| 205 | ||
| 206 | printf " -%s, --%-${LEN}s%s\n" "${arg[0]}" "${arg[1]}" "${arg[2]}" >&2 | |
| 207 | done | |
| 208 | ||
| 209 | # Sort the arguments to make sure no duplicates exist. | |
| 210 | duplicates=$(echo "${duplicates[@]}" | tr ' ' '\n' | sort | uniq -c -d) | |
| 211 | ||
| 212 | # Warn the developer that there's a duplicate command-line option. | |
| 213 | if [ -n "${duplicates}" ]; then | |
| 214 | # Trim all the whitespaces | |
| 215 | duplicates=$(echo "${duplicates}" | xargs echo -n) | |
| 216 | error "Duplicate command-line argument exists: ${duplicates}" | |
| 217 | fi | |
| 218 | ||
| 219 | return 0 | |
| 220 | } | |
| 221 | ||
| 222 | # ----------------------------------------------------------------------------- | |
| 223 | # Write coloured text to standard output. | |
| 224 | # | |
| 225 | # $1 - Text to write | |
| 226 | # $2 - Text's colour | |
| 227 | # ----------------------------------------------------------------------------- | |
| 228 | coloured_text() { | |
| 229 | printf "%b%s%b\n" "$2" "$1" "${COLOUR_OFF}" | |
| 230 | } | |
| 231 | ||
| 232 | # ----------------------------------------------------------------------------- | |
| 233 | # Write a warning message to standard output. | |
| 234 | # | |
| 235 | # $1 - Text to write | |
| 236 | # ----------------------------------------------------------------------------- | |
| 237 | warning() { | |
| 238 | coloured_text "$1" "${COLOUR_WARNING}" | |
| 239 | } | |
| 240 | ||
| 241 | # ----------------------------------------------------------------------------- | |
| 242 | # Write an error message to standard output. | |
| 243 | # | |
| 244 | # $1 - Text to write | |
| 245 | # ----------------------------------------------------------------------------- | |
| 246 | error() { | |
| 247 | coloured_text "$1" "${COLOUR_ERROR}" | |
| 248 | } | |
| 249 | ||
| 250 | # ----------------------------------------------------------------------------- | |
| 251 | # Write a timestamp and message to standard output. | |
| 252 | # | |
| 253 | # $1 - Text to write | |
| 254 | # ----------------------------------------------------------------------------- | |
| 255 | utile_log() { | |
| 256 | printf "[%s] " "$(date +%H:%M:%S.%4N)" | |
| 257 | coloured_text "$1" "${COLOUR_LOGGING}" | |
| 258 | } | |
| 259 | ||
| 260 | # ----------------------------------------------------------------------------- | |
| 261 | # Perform no operations. | |
| 262 | # | |
| 263 | # return 1 - Success | |
| 264 | # ----------------------------------------------------------------------------- | |
| 265 | noop() { | |
| 266 | return 1 | |
| 267 | } | |
| 268 | ||
| 269 | # ----------------------------------------------------------------------------- | |
| 270 | # Exit the program with a given exit code. | |
| 271 | # | |
| 272 | # $1 - Exit code | |
| 273 | # ----------------------------------------------------------------------------- | |
| 274 | terminate() { | |
| 275 | exit "$1" | |
| 276 | } | |
| 277 | ||
| 278 | # ----------------------------------------------------------------------------- | |
| 279 | # Set global variables from command-line arguments. | |
| 280 | # ----------------------------------------------------------------------------- | |
| 281 | arguments() { | |
| 282 | while [ "$#" -gt "0" ]; do | |
| 283 | local consume=1 | |
| 284 | ||
| 285 | case "$1" in | |
| 286 | -V|--verbose) | |
| 287 | log=utile_log | |
| 288 | ;; | |
| 289 | -h|-\?|--help) | |
| 290 | usage=utile_usage | |
| 291 | ;; | |
| 292 | *) | |
| 293 | set +e | |
| 294 | argument "$@" | |
| 295 | consume=$? | |
| 296 | set -e | |
| 297 | ;; | |
| 298 | esac | |
| 299 | ||
| 300 | shift ${consume} | |
| 301 | done | |
| 302 | } | |
| 303 | ||
| 304 | # ----------------------------------------------------------------------------- | |
| 305 | # Parses a single command-line argument. This must return a value greater | |
| 306 | # than or equal to 1, otherwise parsing the command-line arguments will | |
| 307 | # loop indefinitely. | |
| 308 | # | |
| 309 | # @return The number of arguments to consume (1 by default). | |
| 310 | # ----------------------------------------------------------------------------- | |
| 311 | argument() { | |
| 312 | return 1 | |
| 313 | } | |
| 314 | ||
| 315 | # ANSI colour escape sequences. | |
| 316 | readonly COLOUR_BLUE='\033[1;34m' | |
| 317 | readonly COLOUR_PINK='\033[1;35m' | |
| 318 | readonly COLOUR_DKGRAY='\033[30m' | |
| 319 | readonly COLOUR_DKRED='\033[31m' | |
| 320 | readonly COLOUR_LTRED='\033[1;31m' | |
| 321 | readonly COLOUR_YELLOW='\033[1;33m' | |
| 322 | readonly COLOUR_OFF='\033[0m' | |
| 323 | ||
| 324 | # Colour definitions used by script. | |
| 325 | COLOUR_LOGGING=${COLOUR_BLUE} | |
| 326 | COLOUR_WARNING=${COLOUR_YELLOW} | |
| 327 | COLOUR_ERROR=${COLOUR_LTRED} | |
| 328 | ||
| 329 | # Define required commands to check when script starts. | |
| 330 | DEPENDENCIES=( | |
| 331 | "awk,https://www.gnu.org/software/gawk/manual/gawk.html" | |
| 332 | "cut,https://www.gnu.org/software/coreutils" | |
| 333 | ) | |
| 334 | ||
| 335 | # Define help for command-line arguments. | |
| 336 | ARGUMENTS=( | |
| 337 | "V,verbose,Log messages while processing" | |
| 338 | "h,help,Show this help message then exit" | |
| 339 | ) | |
| 340 | ||
| 341 | # These functions may be set to utile delegates while parsing arguments. | |
| 342 | usage=noop | |
| 343 | log=noop | |
| 344 | ||
| 1 | 345 |
| 1 | plugins { | |
| 2 | id 'application' | |
| 3 | id 'org.openjfx.javafxplugin' version '0.0.8' | |
| 4 | id 'com.palantir.git-version' version '0.12.3' | |
| 5 | id 'org.beryx.jlink' version '2.16.2' | |
| 6 | } | |
| 7 | ||
| 8 | repositories { | |
| 9 | mavenCentral() | |
| 10 | jcenter() | |
| 11 | ||
| 12 | maven { | |
| 13 | url 'https://oss.sonatype.org/content/repositories/snapshots/' | |
| 14 | } | |
| 15 | ||
| 16 | maven { | |
| 17 | url "https://nexus.bedatadriven.com/content/groups/public" | |
| 18 | } | |
| 19 | } | |
| 20 | ||
| 21 | // Assume a cross-platform überjar unless targetOs is set. | |
| 22 | String[] os = ["win", "mac", "linux"] | |
| 23 | ||
| 24 | if (project.hasProperty('targetOs')) { | |
| 25 | if ("windows" == targetOs) { | |
| 26 | os = ["win"] | |
| 27 | } else { | |
| 28 | os = [targetOs] | |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | dependencies { | |
| 33 | // JavaFX | |
| 34 | implementation 'org.reactfx:reactfx:1.4.1' | |
| 35 | implementation 'org.controlsfx:controlsfx:11.0.2' | |
| 36 | implementation 'org.fxmisc.richtext:richtextfx:0.10.5' | |
| 37 | implementation 'org.fxmisc.wellbehaved:wellbehavedfx:0.3.3' | |
| 38 | implementation 'com.miglayout:miglayout-javafx:5.2' | |
| 39 | implementation('com.dlsc.preferencesfx:preferencesfx-core:11.6.0') { | |
| 40 | exclude group: 'org.openjfx' | |
| 41 | } | |
| 42 | implementation('de.jensd:fontawesomefx-commons:11.0') { | |
| 43 | exclude group: 'org.openjfx' | |
| 44 | } | |
| 45 | implementation('de.jensd:fontawesomefx-fontawesome:4.7.0-11') { | |
| 46 | exclude group: 'org.openjfx' | |
| 47 | } | |
| 48 | ||
| 49 | // Markdown | |
| 50 | implementation 'com.vladsch.flexmark:flexmark:0.62.2' | |
| 51 | implementation 'com.vladsch.flexmark:flexmark-ext-definition:0.62.2' | |
| 52 | implementation 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.62.2' | |
| 53 | implementation 'com.vladsch.flexmark:flexmark-ext-superscript:0.62.2' | |
| 54 | implementation 'com.vladsch.flexmark:flexmark-ext-tables:0.62.2' | |
| 55 | implementation 'com.vladsch.flexmark:flexmark-ext-typographic:0.62.2' | |
| 56 | ||
| 57 | // YAML | |
| 58 | implementation 'com.fasterxml.jackson.core:jackson-core:2.11.2' | |
| 59 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.2' | |
| 60 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.2' | |
| 61 | implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.2' | |
| 62 | implementation 'org.yaml:snakeyaml:1.26' | |
| 63 | ||
| 64 | // XML and XSL | |
| 65 | implementation 'com.ximpleware:vtd-xml:2.13.4' | |
| 66 | implementation 'net.sf.saxon:Saxon-HE:10.1' | |
| 67 | implementation 'xalan:xalan:2.7.2' | |
| 68 | ||
| 69 | // HTML parsing and rendering | |
| 70 | implementation 'org.jsoup:jsoup:1.13.1' | |
| 71 | implementation 'org.xhtmlrenderer:flying-saucer-core:9.1.20' | |
| 72 | ||
| 73 | // R | |
| 74 | implementation 'org.renjin:renjin-script-engine:3.5-beta76' | |
| 75 | ||
| 76 | // SVG | |
| 77 | implementation 'org.apache.xmlgraphics:batik-anim:1.13' | |
| 78 | implementation 'org.apache.xmlgraphics:batik-awt-util:1.13' | |
| 79 | implementation 'org.apache.xmlgraphics:batik-bridge:1.13' | |
| 80 | implementation 'org.apache.xmlgraphics:batik-css:1.13' | |
| 81 | implementation 'org.apache.xmlgraphics:batik-dom:1.13' | |
| 82 | implementation 'org.apache.xmlgraphics:batik-ext:1.13' | |
| 83 | implementation 'org.apache.xmlgraphics:batik-gvt:1.13' | |
| 84 | implementation 'org.apache.xmlgraphics:batik-parser:1.13' | |
| 85 | implementation 'org.apache.xmlgraphics:batik-script:1.13' | |
| 86 | implementation 'org.apache.xmlgraphics:batik-svg-dom:1.13' | |
| 87 | implementation 'org.apache.xmlgraphics:batik-svggen:1.13' | |
| 88 | implementation 'org.apache.xmlgraphics:batik-transcoder:1.13' | |
| 89 | implementation 'org.apache.xmlgraphics:batik-util:1.13' | |
| 90 | implementation 'org.apache.xmlgraphics:batik-xml:1.13' | |
| 91 | ||
| 92 | // Spelling, TeX | |
| 93 | implementation fileTree(include: ['**/*.jar'], dir: 'libs') | |
| 94 | ||
| 95 | // Misc. | |
| 96 | implementation 'org.ahocorasick:ahocorasick:0.4.0' | |
| 97 | implementation 'org.apache.commons:commons-configuration2:2.7' | |
| 98 | implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' | |
| 99 | implementation 'javax.validation:validation-api:2.0.1.Final' | |
| 100 | ||
| 101 | def fx = ['controls', 'graphics', 'fxml', 'swing'] | |
| 102 | ||
| 103 | fx.each { fxitem -> | |
| 104 | os.each { ositem -> | |
| 105 | runtimeOnly "org.openjfx:javafx-${fxitem}:${javafx.version}:${ositem}" | |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | testImplementation('org.junit.jupiter:junit-jupiter-api:5.4.2') | |
| 110 | testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2') | |
| 111 | } | |
| 112 | ||
| 113 | javafx { | |
| 114 | version = "14" | |
| 115 | modules = ['javafx.controls', 'javafx.swing'] | |
| 116 | } | |
| 117 | ||
| 118 | compileJava { | |
| 119 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" | |
| 120 | } | |
| 121 | ||
| 122 | application { | |
| 123 | applicationName = 'scrivenvar' | |
| 124 | mainClassName = "com.${applicationName}.Main" | |
| 125 | ||
| 126 | applicationDefaultJvmArgs = [ | |
| 127 | "--add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED", | |
| 128 | "--add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED", | |
| 129 | "--add-opens=javafx.graphics/com.sun.javafx.css=ALL-UNNAMED", | |
| 130 | ] | |
| 131 | } | |
| 132 | ||
| 133 | version = gitVersion() | |
| 134 | ||
| 135 | def launcherClassName = "com.${applicationName}.Launcher" | |
| 136 | ||
| 137 | def propertiesFile = new File("src/main/resources/com/${applicationName}/app.properties") | |
| 138 | propertiesFile.write("application.version=${version}") | |
| 139 | ||
| 140 | jar { | |
| 141 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE | |
| 142 | ||
| 143 | manifest { | |
| 144 | attributes 'Main-Class': launcherClassName | |
| 145 | } | |
| 146 | ||
| 147 | from { | |
| 148 | (configurations.runtimeClasspath.findAll { !it.path.endsWith(".pom") }).collect { | |
| 149 | it.isDirectory() ? it : zipTree(it) | |
| 150 | } | |
| 151 | } | |
| 152 | ||
| 153 | archiveFileName = "${applicationName}.jar" | |
| 154 | ||
| 155 | exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' | |
| 156 | } | |
| 157 | ||
| 158 | distributions { | |
| 159 | main { | |
| 160 | distributionBaseName = applicationName | |
| 161 | contents { | |
| 162 | from { ['LICENSE.md', 'README.md'] } | |
| 163 | into('images') { | |
| 164 | from { 'images' } | |
| 165 | } | |
| 166 | } | |
| 167 | } | |
| 168 | } | |
| 169 | ||
| 170 | test { | |
| 171 | useJUnitPlatform() | |
| 172 | } | |
| 173 | ||
| 1 | 174 |
| 1 | ## Documents | |
| 2 | ||
| 3 | See the following documents for more information: | |
| 4 | ||
| 5 | * [definitions.md](definitions.md) -- Definitions and interpolation | |
| 6 | * [r.md](r.md) -- Call R functions within R Markdown documents | |
| 7 | * [texample.md](texample.md) -- Numerous examples of formulas | |
| 8 | * [svg.md](svg.md) -- Fix known issues with displaying SVG files | |
| 9 | * [credits.md](credits.md) -- Thanks to authors of contributing projects | |
| 10 | ||
| 1 | 11 |
| 1 | # Credits | |
| 2 | ||
| 3 | * Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx) | |
| 4 | * Tomas Mikula: [RichTextFX](https://github.com/TomasMikula/RichTextFX), [ReactFX](https://github.com/TomasMikula/ReactFX), [WellBehavedFX](https://github.com/TomasMikula/WellBehavedFX), [Flowless](https://github.com/TomasMikula/Flowless), and [UndoFX](https://github.com/TomasMikula/UndoFX) | |
| 5 | * Mikael Grev: [MigLayout](http://www.miglayout.com/) | |
| 6 | * Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java) | |
| 7 | * Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx) | |
| 8 | * Dieter Holz, [PreferencesFX](https://github.com/dlsc-software-consulting-gmbh/PreferencesFX) | |
| 9 | * David Croft, [File Preferences](http://www.davidc.net/programming/java/java-preferences-using-file-backing-store) | |
| 10 | * Alex Bertram, [Renjin](https://www.renjin.org/) | |
| 11 | * Vladimir Schneider: [flexmark](https://github.com/vsch/flexmark-java) | |
| 12 | * Michael Kay, [XSLT Processor](http://www.saxonica.com/) | |
| 13 | * Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet) | |
| 14 | ||
| 1 | 15 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to use the application. | |
| 4 | ||
| 5 | # Variable definitions | |
| 6 | ||
| 7 | Variable definitions provide a way to insert key names having associated values into a document. The variable names and values are declared inside an external file using the [YAML](http://www.yaml.org/) file format. Simply put, variables are written in the file as follows: | |
| 8 | ||
| 9 | ``` | |
| 10 | key: value | |
| 11 | ``` | |
| 12 | ||
| 13 | Any number of variables can be defined, in any order: | |
| 14 | ||
| 15 | ``` | |
| 16 | key_1: Value 1 | |
| 17 | key_2: Value 2 | |
| 18 | ``` | |
| 19 | ||
| 20 | Variables can reference other variables by enclosing the key name within dollar symbols: | |
| 21 | ||
| 22 | ``` | |
| 23 | key: Value | |
| 24 | key_1: $key$ 1 | |
| 25 | key_2: $key$ 2 | |
| 26 | ``` | |
| 27 | ||
| 28 | Variables can use a nested structure to help group related information: | |
| 29 | ||
| 30 | ``` | |
| 31 | novel: | |
| 32 | title: Book Title | |
| 33 | author: Author Name | |
| 34 | isbn: 978-3-16-148410-0 | |
| 35 | ``` | |
| 36 | ||
| 37 | Use a period to reference nested keys, such as: | |
| 38 | ||
| 39 | ``` | |
| 40 | novel: | |
| 41 | author: Author Name | |
| 42 | copyright: | |
| 43 | owner: $novel.author$ | |
| 44 | ``` | |
| 45 | ||
| 46 | Save the variable definitions in a file having an extension of `.yaml` or `.yml`. | |
| 47 | ||
| 48 | # Document editing | |
| 49 | ||
| 50 | The application's purpose is to completely separate the document's content from its presentation. To achieve this, documents are composed using a [plain text](http://spec.commonmark.org/0.28/) format. | |
| 51 | ||
| 52 | ## Create document | |
| 53 | ||
| 54 | Start a new document as follows: | |
| 55 | ||
| 56 | 1. Start the application. | |
| 57 | 1. Click **File → New** to create an empty document to edit. | |
| 58 | 1. Click **File → Open** to open a variable definition file. | |
| 59 | 1. Change **Source Files** to **Definition Files** to list definition files. | |
| 60 | 1. Browse to and select a file saved with a `.yaml` or `.yml` extension. | |
| 61 | 1. Click **Open**. | |
| 62 | ||
| 63 | The variable definitions appear in the variable definition pane under the heading of **Definitions**. | |
| 64 | ||
| 65 | ## Edit document | |
| 66 | ||
| 67 | Edit the document as normal. Notice how the preview pane updates as new content is added. The toolbar shows various icons that perform different formatting operations. Try them to see how they appear in the preview pane. Other operations not shown on the toolbar include: | |
| 68 | ||
| 69 | * Struck text (enclose the words within `~~` and `~~`) | |
| 70 | * Horizontal rule (use `---` on an otherwise empty line). | |
| 71 | ||
| 72 | The preview pane shows one way to interpret and format the document, but many other presentations are possible. | |
| 73 | ||
| 74 | ## Insert variable | |
| 75 | ||
| 76 | Let's assume that the variable definitions loaded into the application include: | |
| 77 | ||
| 78 | ``` | |
| 79 | novel: | |
| 80 | title: Diary of $novel.author$ | |
| 81 | author: Anne Frank | |
| 82 | ``` | |
| 83 | ||
| 84 | To reference a variable, type in the key name enclosed within dollar symbols, such as: | |
| 85 | ||
| 86 | ``` | |
| 87 | The novel "$novel.title$" is one of the most widely read books in the world. | |
| 88 | ``` | |
| 89 | ||
| 90 | The preview pane shows: | |
| 91 | ||
| 92 | > The novel "Diary of Anne Frank" is one of the most widely read books in the world. | |
| 93 | ||
| 94 | As it is laborious to type in variable names, it is possible to inject the variable name using autocomplete. Accomplish this as follows: | |
| 95 | ||
| 96 | 1. Create a new file. | |
| 97 | 1. Type in a partial variable value, such as **Dia**. | |
| 98 | 1. Press `Ctrl+Space` (hold down the `Control` key and tap the spacebar). | |
| 99 | ||
| 100 | The editor shows: | |
| 101 | ||
| 102 | ``` | |
| 103 | $novel.title$ | |
| 104 | ``` | |
| 105 | ||
| 106 | The preview pane shows: | |
| 107 | ||
| 108 | ``` | |
| 109 | Diary of Anne Frank | |
| 110 | ``` | |
| 111 | ||
| 112 | The variable name is inserted into the document and the preview pane shows the variable's value. | |
| 113 | ||
| 1 | 114 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 10 | inkscape:export-ydpi="150.0097" | |
| 11 | inkscape:export-xdpi="150.0097" | |
| 12 | sodipodi:docname="architecture.svg" | |
| 13 | viewBox="0 0 764.4414 811.46748" | |
| 14 | height="811.46747" | |
| 15 | width="764.44141" | |
| 16 | id="svg4610" | |
| 17 | version="1.2" | |
| 18 | inkscape:version="1.0 (4035a4fb49, 2020-05-01)"> | |
| 19 | <metadata | |
| 20 | id="metadata4616"> | |
| 21 | <rdf:RDF> | |
| 22 | <cc:Work | |
| 23 | rdf:about=""> | |
| 24 | <dc:format>image/svg+xml</dc:format> | |
| 25 | <dc:type | |
| 26 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 27 | <dc:title /> | |
| 28 | </cc:Work> | |
| 29 | </rdf:RDF> | |
| 30 | </metadata> | |
| 31 | <defs | |
| 32 | id="defs4614"> | |
| 33 | <marker | |
| 34 | inkscape:stockid="Arrow1Mend" | |
| 35 | orient="auto" | |
| 36 | refY="0" | |
| 37 | refX="0" | |
| 38 | id="marker10933" | |
| 39 | style="overflow:visible" | |
| 40 | inkscape:isstock="true"> | |
| 41 | <path | |
| 42 | id="path10931" | |
| 43 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 44 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 45 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 46 | inkscape:connector-curvature="0" /> | |
| 47 | </marker> | |
| 48 | <marker | |
| 49 | inkscape:stockid="Arrow1Mend" | |
| 50 | orient="auto" | |
| 51 | refY="0" | |
| 52 | refX="0" | |
| 53 | id="marker9893" | |
| 54 | style="overflow:visible" | |
| 55 | inkscape:isstock="true"> | |
| 56 | <path | |
| 57 | id="path9891" | |
| 58 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 59 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 60 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 61 | inkscape:connector-curvature="0" /> | |
| 62 | </marker> | |
| 63 | <marker | |
| 64 | inkscape:collect="always" | |
| 65 | inkscape:isstock="true" | |
| 66 | style="overflow:visible" | |
| 67 | id="marker9767" | |
| 68 | refX="0" | |
| 69 | refY="0" | |
| 70 | orient="auto" | |
| 71 | inkscape:stockid="Arrow1Mend"> | |
| 72 | <path | |
| 73 | inkscape:connector-curvature="0" | |
| 74 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 75 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 76 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 77 | id="path9765" /> | |
| 78 | </marker> | |
| 79 | <marker | |
| 80 | inkscape:collect="always" | |
| 81 | inkscape:stockid="Arrow1Mend" | |
| 82 | orient="auto" | |
| 83 | refY="0" | |
| 84 | refX="0" | |
| 85 | id="marker9761" | |
| 86 | style="overflow:visible" | |
| 87 | inkscape:isstock="true"> | |
| 88 | <path | |
| 89 | id="path9759" | |
| 90 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 91 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 92 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 93 | inkscape:connector-curvature="0" /> | |
| 94 | </marker> | |
| 95 | <marker | |
| 96 | inkscape:isstock="true" | |
| 97 | style="overflow:visible" | |
| 98 | id="marker9750" | |
| 99 | refX="0" | |
| 100 | refY="0" | |
| 101 | orient="auto" | |
| 102 | inkscape:stockid="Arrow1Mend"> | |
| 103 | <path | |
| 104 | inkscape:connector-curvature="0" | |
| 105 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 106 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 107 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 108 | id="path9748" /> | |
| 109 | </marker> | |
| 110 | <marker | |
| 111 | inkscape:isstock="true" | |
| 112 | style="overflow:visible" | |
| 113 | id="marker9715" | |
| 114 | refX="0" | |
| 115 | refY="0" | |
| 116 | orient="auto" | |
| 117 | inkscape:stockid="Arrow1Mend"> | |
| 118 | <path | |
| 119 | inkscape:connector-curvature="0" | |
| 120 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 121 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 122 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 123 | id="path9713" /> | |
| 124 | </marker> | |
| 125 | <marker | |
| 126 | inkscape:collect="always" | |
| 127 | inkscape:stockid="Arrow1Mend" | |
| 128 | orient="auto" | |
| 129 | refY="0" | |
| 130 | refX="0" | |
| 131 | id="marker9685" | |
| 132 | style="overflow:visible" | |
| 133 | inkscape:isstock="true"> | |
| 134 | <path | |
| 135 | id="path9683" | |
| 136 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 137 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 138 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 139 | inkscape:connector-curvature="0" /> | |
| 140 | </marker> | |
| 141 | <marker | |
| 142 | inkscape:collect="always" | |
| 143 | inkscape:stockid="Arrow1Mend" | |
| 144 | orient="auto" | |
| 145 | refY="0" | |
| 146 | refX="0" | |
| 147 | id="marker9679" | |
| 148 | style="overflow:visible" | |
| 149 | inkscape:isstock="true"> | |
| 150 | <path | |
| 151 | id="path9677" | |
| 152 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 153 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 154 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 155 | inkscape:connector-curvature="0" /> | |
| 156 | </marker> | |
| 157 | <marker | |
| 158 | inkscape:collect="always" | |
| 159 | inkscape:isstock="true" | |
| 160 | style="overflow:visible" | |
| 161 | id="marker9640" | |
| 162 | refX="0" | |
| 163 | refY="0" | |
| 164 | orient="auto" | |
| 165 | inkscape:stockid="Arrow1Mend"> | |
| 166 | <path | |
| 167 | inkscape:connector-curvature="0" | |
| 168 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 169 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 170 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 171 | id="path9638" /> | |
| 172 | </marker> | |
| 173 | <marker | |
| 174 | inkscape:collect="always" | |
| 175 | inkscape:isstock="true" | |
| 176 | style="overflow:visible" | |
| 177 | id="marker9513" | |
| 178 | refX="0" | |
| 179 | refY="0" | |
| 180 | orient="auto" | |
| 181 | inkscape:stockid="Arrow1Mend"> | |
| 182 | <path | |
| 183 | inkscape:connector-curvature="0" | |
| 184 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 185 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 186 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 187 | id="path9511" /> | |
| 188 | </marker> | |
| 189 | <marker | |
| 190 | inkscape:stockid="Arrow1Mend" | |
| 191 | orient="auto" | |
| 192 | refY="0" | |
| 193 | refX="0" | |
| 194 | id="marker9509" | |
| 195 | style="overflow:visible" | |
| 196 | inkscape:isstock="true"> | |
| 197 | <path | |
| 198 | id="path9507" | |
| 199 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 200 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 201 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 202 | inkscape:connector-curvature="0" /> | |
| 203 | </marker> | |
| 204 | <marker | |
| 205 | inkscape:isstock="true" | |
| 206 | style="overflow:visible" | |
| 207 | id="marker9505" | |
| 208 | refX="0" | |
| 209 | refY="0" | |
| 210 | orient="auto" | |
| 211 | inkscape:stockid="Arrow1Mend"> | |
| 212 | <path | |
| 213 | inkscape:connector-curvature="0" | |
| 214 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 215 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 216 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 217 | id="path9503" /> | |
| 218 | </marker> | |
| 219 | <marker | |
| 220 | inkscape:collect="always" | |
| 221 | inkscape:stockid="Arrow1Mend" | |
| 222 | orient="auto" | |
| 223 | refY="0" | |
| 224 | refX="0" | |
| 225 | id="marker9479" | |
| 226 | style="overflow:visible" | |
| 227 | inkscape:isstock="true"> | |
| 228 | <path | |
| 229 | id="path9477" | |
| 230 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 231 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 232 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 233 | inkscape:connector-curvature="0" /> | |
| 234 | </marker> | |
| 235 | <clipPath | |
| 236 | id="ID000001"> | |
| 237 | <rect | |
| 238 | id="rect6" | |
| 239 | height="961.125" | |
| 240 | width="1381.6169" | |
| 241 | y="-43.688" | |
| 242 | x="-62.683998" /> | |
| 243 | </clipPath> | |
| 244 | <filter | |
| 245 | id="filter2842" | |
| 246 | inkscape:label="Drop Shadow" | |
| 247 | style="color-interpolation-filters:sRGB;"> | |
| 248 | <feFlood | |
| 249 | id="feFlood2832" | |
| 250 | result="flood" | |
| 251 | flood-color="rgb(0,0,0)" | |
| 252 | flood-opacity="0.498039" /> | |
| 253 | <feComposite | |
| 254 | id="feComposite2834" | |
| 255 | result="composite1" | |
| 256 | operator="in" | |
| 257 | in2="SourceGraphic" | |
| 258 | in="flood" /> | |
| 259 | <feGaussianBlur | |
| 260 | id="feGaussianBlur2836" | |
| 261 | result="blur" | |
| 262 | stdDeviation="2" | |
| 263 | in="composite1" /> | |
| 264 | <feOffset | |
| 265 | id="feOffset2838" | |
| 266 | result="offset" | |
| 267 | dy="3" | |
| 268 | dx="3" /> | |
| 269 | <feComposite | |
| 270 | id="feComposite2840" | |
| 271 | result="composite2" | |
| 272 | operator="over" | |
| 273 | in2="offset" | |
| 274 | in="SourceGraphic" /> | |
| 275 | </filter> | |
| 276 | <filter | |
| 277 | id="filter2854" | |
| 278 | inkscape:label="Drop Shadow" | |
| 279 | style="color-interpolation-filters:sRGB;"> | |
| 280 | <feFlood | |
| 281 | id="feFlood2844" | |
| 282 | result="flood" | |
| 283 | flood-color="rgb(0,0,0)" | |
| 284 | flood-opacity="0.498039" /> | |
| 285 | <feComposite | |
| 286 | id="feComposite2846" | |
| 287 | result="composite1" | |
| 288 | operator="in" | |
| 289 | in2="SourceGraphic" | |
| 290 | in="flood" /> | |
| 291 | <feGaussianBlur | |
| 292 | id="feGaussianBlur2848" | |
| 293 | result="blur" | |
| 294 | stdDeviation="2" | |
| 295 | in="composite1" /> | |
| 296 | <feOffset | |
| 297 | id="feOffset2850" | |
| 298 | result="offset" | |
| 299 | dy="3" | |
| 300 | dx="3" /> | |
| 301 | <feComposite | |
| 302 | id="feComposite2852" | |
| 303 | result="composite2" | |
| 304 | operator="over" | |
| 305 | in2="offset" | |
| 306 | in="SourceGraphic" /> | |
| 307 | </filter> | |
| 308 | <filter | |
| 309 | id="filter2866" | |
| 310 | inkscape:label="Drop Shadow" | |
| 311 | style="color-interpolation-filters:sRGB;"> | |
| 312 | <feFlood | |
| 313 | id="feFlood2856" | |
| 314 | result="flood" | |
| 315 | flood-color="rgb(0,0,0)" | |
| 316 | flood-opacity="0.498039" /> | |
| 317 | <feComposite | |
| 318 | id="feComposite2858" | |
| 319 | result="composite1" | |
| 320 | operator="in" | |
| 321 | in2="SourceGraphic" | |
| 322 | in="flood" /> | |
| 323 | <feGaussianBlur | |
| 324 | id="feGaussianBlur2860" | |
| 325 | result="blur" | |
| 326 | stdDeviation="2" | |
| 327 | in="composite1" /> | |
| 328 | <feOffset | |
| 329 | id="feOffset2862" | |
| 330 | result="offset" | |
| 331 | dy="3" | |
| 332 | dx="3" /> | |
| 333 | <feComposite | |
| 334 | id="feComposite2864" | |
| 335 | result="composite2" | |
| 336 | operator="over" | |
| 337 | in2="offset" | |
| 338 | in="SourceGraphic" /> | |
| 339 | </filter> | |
| 340 | <filter | |
| 341 | id="filter2878" | |
| 342 | inkscape:label="Drop Shadow" | |
| 343 | style="color-interpolation-filters:sRGB;"> | |
| 344 | <feFlood | |
| 345 | id="feFlood2868" | |
| 346 | result="flood" | |
| 347 | flood-color="rgb(0,0,0)" | |
| 348 | flood-opacity="0.498039" /> | |
| 349 | <feComposite | |
| 350 | id="feComposite2870" | |
| 351 | result="composite1" | |
| 352 | operator="in" | |
| 353 | in2="SourceGraphic" | |
| 354 | in="flood" /> | |
| 355 | <feGaussianBlur | |
| 356 | id="feGaussianBlur2872" | |
| 357 | result="blur" | |
| 358 | stdDeviation="2" | |
| 359 | in="composite1" /> | |
| 360 | <feOffset | |
| 361 | id="feOffset2874" | |
| 362 | result="offset" | |
| 363 | dy="3" | |
| 364 | dx="3" /> | |
| 365 | <feComposite | |
| 366 | id="feComposite2876" | |
| 367 | result="composite2" | |
| 368 | operator="over" | |
| 369 | in2="offset" | |
| 370 | in="SourceGraphic" /> | |
| 371 | </filter> | |
| 372 | <filter | |
| 373 | id="filter2890" | |
| 374 | inkscape:label="Drop Shadow" | |
| 375 | style="color-interpolation-filters:sRGB;"> | |
| 376 | <feFlood | |
| 377 | id="feFlood2880" | |
| 378 | result="flood" | |
| 379 | flood-color="rgb(0,0,0)" | |
| 380 | flood-opacity="0.498039" /> | |
| 381 | <feComposite | |
| 382 | id="feComposite2882" | |
| 383 | result="composite1" | |
| 384 | operator="in" | |
| 385 | in2="SourceGraphic" | |
| 386 | in="flood" /> | |
| 387 | <feGaussianBlur | |
| 388 | id="feGaussianBlur2884" | |
| 389 | result="blur" | |
| 390 | stdDeviation="2" | |
| 391 | in="composite1" /> | |
| 392 | <feOffset | |
| 393 | id="feOffset2886" | |
| 394 | result="offset" | |
| 395 | dy="3" | |
| 396 | dx="3" /> | |
| 397 | <feComposite | |
| 398 | id="feComposite2888" | |
| 399 | result="composite2" | |
| 400 | operator="over" | |
| 401 | in2="offset" | |
| 402 | in="SourceGraphic" /> | |
| 403 | </filter> | |
| 404 | <filter | |
| 405 | id="filter2902" | |
| 406 | inkscape:label="Drop Shadow" | |
| 407 | style="color-interpolation-filters:sRGB;"> | |
| 408 | <feFlood | |
| 409 | id="feFlood2892" | |
| 410 | result="flood" | |
| 411 | flood-color="rgb(0,0,0)" | |
| 412 | flood-opacity="0.498039" /> | |
| 413 | <feComposite | |
| 414 | id="feComposite2894" | |
| 415 | result="composite1" | |
| 416 | operator="in" | |
| 417 | in2="SourceGraphic" | |
| 418 | in="flood" /> | |
| 419 | <feGaussianBlur | |
| 420 | id="feGaussianBlur2896" | |
| 421 | result="blur" | |
| 422 | stdDeviation="2" | |
| 423 | in="composite1" /> | |
| 424 | <feOffset | |
| 425 | id="feOffset2898" | |
| 426 | result="offset" | |
| 427 | dy="3" | |
| 428 | dx="3" /> | |
| 429 | <feComposite | |
| 430 | id="feComposite2900" | |
| 431 | result="composite2" | |
| 432 | operator="over" | |
| 433 | in2="offset" | |
| 434 | in="SourceGraphic" /> | |
| 435 | </filter> | |
| 436 | <filter | |
| 437 | id="filter2914" | |
| 438 | inkscape:label="Drop Shadow" | |
| 439 | style="color-interpolation-filters:sRGB;"> | |
| 440 | <feFlood | |
| 441 | id="feFlood2904" | |
| 442 | result="flood" | |
| 443 | flood-color="rgb(0,0,0)" | |
| 444 | flood-opacity="0.498039" /> | |
| 445 | <feComposite | |
| 446 | id="feComposite2906" | |
| 447 | result="composite1" | |
| 448 | operator="in" | |
| 449 | in2="SourceGraphic" | |
| 450 | in="flood" /> | |
| 451 | <feGaussianBlur | |
| 452 | id="feGaussianBlur2908" | |
| 453 | result="blur" | |
| 454 | stdDeviation="2" | |
| 455 | in="composite1" /> | |
| 456 | <feOffset | |
| 457 | id="feOffset2910" | |
| 458 | result="offset" | |
| 459 | dy="3" | |
| 460 | dx="3" /> | |
| 461 | <feComposite | |
| 462 | id="feComposite2912" | |
| 463 | result="composite2" | |
| 464 | operator="over" | |
| 465 | in2="offset" | |
| 466 | in="SourceGraphic" /> | |
| 467 | </filter> | |
| 468 | <filter | |
| 469 | id="filter2926" | |
| 470 | inkscape:label="Drop Shadow" | |
| 471 | style="color-interpolation-filters:sRGB;"> | |
| 472 | <feFlood | |
| 473 | id="feFlood2916" | |
| 474 | result="flood" | |
| 475 | flood-color="rgb(0,0,0)" | |
| 476 | flood-opacity="0.498039" /> | |
| 477 | <feComposite | |
| 478 | id="feComposite2918" | |
| 479 | result="composite1" | |
| 480 | operator="in" | |
| 481 | in2="SourceGraphic" | |
| 482 | in="flood" /> | |
| 483 | <feGaussianBlur | |
| 484 | id="feGaussianBlur2920" | |
| 485 | result="blur" | |
| 486 | stdDeviation="2" | |
| 487 | in="composite1" /> | |
| 488 | <feOffset | |
| 489 | id="feOffset2922" | |
| 490 | result="offset" | |
| 491 | dy="3" | |
| 492 | dx="3" /> | |
| 493 | <feComposite | |
| 494 | id="feComposite2924" | |
| 495 | result="composite2" | |
| 496 | operator="over" | |
| 497 | in2="offset" | |
| 498 | in="SourceGraphic" /> | |
| 499 | </filter> | |
| 500 | <filter | |
| 501 | id="filter2938" | |
| 502 | inkscape:label="Drop Shadow" | |
| 503 | style="color-interpolation-filters:sRGB;"> | |
| 504 | <feFlood | |
| 505 | id="feFlood2928" | |
| 506 | result="flood" | |
| 507 | flood-color="rgb(0,0,0)" | |
| 508 | flood-opacity="0.498039" /> | |
| 509 | <feComposite | |
| 510 | id="feComposite2930" | |
| 511 | result="composite1" | |
| 512 | operator="in" | |
| 513 | in2="SourceGraphic" | |
| 514 | in="flood" /> | |
| 515 | <feGaussianBlur | |
| 516 | id="feGaussianBlur2932" | |
| 517 | result="blur" | |
| 518 | stdDeviation="2" | |
| 519 | in="composite1" /> | |
| 520 | <feOffset | |
| 521 | id="feOffset2934" | |
| 522 | result="offset" | |
| 523 | dy="3" | |
| 524 | dx="3" /> | |
| 525 | <feComposite | |
| 526 | id="feComposite2936" | |
| 527 | result="composite2" | |
| 528 | operator="over" | |
| 529 | in2="offset" | |
| 530 | in="SourceGraphic" /> | |
| 531 | </filter> | |
| 532 | <filter | |
| 533 | id="filter2950" | |
| 534 | inkscape:label="Drop Shadow" | |
| 535 | style="color-interpolation-filters:sRGB;"> | |
| 536 | <feFlood | |
| 537 | id="feFlood2940" | |
| 538 | result="flood" | |
| 539 | flood-color="rgb(0,0,0)" | |
| 540 | flood-opacity="0.498039" /> | |
| 541 | <feComposite | |
| 542 | id="feComposite2942" | |
| 543 | result="composite1" | |
| 544 | operator="in" | |
| 545 | in2="SourceGraphic" | |
| 546 | in="flood" /> | |
| 547 | <feGaussianBlur | |
| 548 | id="feGaussianBlur2944" | |
| 549 | result="blur" | |
| 550 | stdDeviation="2" | |
| 551 | in="composite1" /> | |
| 552 | <feOffset | |
| 553 | id="feOffset2946" | |
| 554 | result="offset" | |
| 555 | dy="3" | |
| 556 | dx="3" /> | |
| 557 | <feComposite | |
| 558 | id="feComposite2948" | |
| 559 | result="composite2" | |
| 560 | operator="over" | |
| 561 | in2="offset" | |
| 562 | in="SourceGraphic" /> | |
| 563 | </filter> | |
| 564 | <filter | |
| 565 | id="filter2962" | |
| 566 | inkscape:label="Drop Shadow" | |
| 567 | style="color-interpolation-filters:sRGB;"> | |
| 568 | <feFlood | |
| 569 | id="feFlood2952" | |
| 570 | result="flood" | |
| 571 | flood-color="rgb(0,0,0)" | |
| 572 | flood-opacity="0.498039" /> | |
| 573 | <feComposite | |
| 574 | id="feComposite2954" | |
| 575 | result="composite1" | |
| 576 | operator="in" | |
| 577 | in2="SourceGraphic" | |
| 578 | in="flood" /> | |
| 579 | <feGaussianBlur | |
| 580 | id="feGaussianBlur2956" | |
| 581 | result="blur" | |
| 582 | stdDeviation="2" | |
| 583 | in="composite1" /> | |
| 584 | <feOffset | |
| 585 | id="feOffset2958" | |
| 586 | result="offset" | |
| 587 | dy="3" | |
| 588 | dx="3" /> | |
| 589 | <feComposite | |
| 590 | id="feComposite2960" | |
| 591 | result="composite2" | |
| 592 | operator="over" | |
| 593 | in2="offset" | |
| 594 | in="SourceGraphic" /> | |
| 595 | </filter> | |
| 596 | <filter | |
| 597 | id="filter2974" | |
| 598 | inkscape:label="Drop Shadow" | |
| 599 | style="color-interpolation-filters:sRGB;"> | |
| 600 | <feFlood | |
| 601 | id="feFlood2964" | |
| 602 | result="flood" | |
| 603 | flood-color="rgb(0,0,0)" | |
| 604 | flood-opacity="0.498039" /> | |
| 605 | <feComposite | |
| 606 | id="feComposite2966" | |
| 607 | result="composite1" | |
| 608 | operator="in" | |
| 609 | in2="SourceGraphic" | |
| 610 | in="flood" /> | |
| 611 | <feGaussianBlur | |
| 612 | id="feGaussianBlur2968" | |
| 613 | result="blur" | |
| 614 | stdDeviation="2" | |
| 615 | in="composite1" /> | |
| 616 | <feOffset | |
| 617 | id="feOffset2970" | |
| 618 | result="offset" | |
| 619 | dy="3" | |
| 620 | dx="3" /> | |
| 621 | <feComposite | |
| 622 | id="feComposite2972" | |
| 623 | result="composite2" | |
| 624 | operator="over" | |
| 625 | in2="offset" | |
| 626 | in="SourceGraphic" /> | |
| 627 | </filter> | |
| 628 | <filter | |
| 629 | id="filter2986" | |
| 630 | inkscape:label="Drop Shadow" | |
| 631 | style="color-interpolation-filters:sRGB;"> | |
| 632 | <feFlood | |
| 633 | id="feFlood2976" | |
| 634 | result="flood" | |
| 635 | flood-color="rgb(0,0,0)" | |
| 636 | flood-opacity="0.498039" /> | |
| 637 | <feComposite | |
| 638 | id="feComposite2978" | |
| 639 | result="composite1" | |
| 640 | operator="in" | |
| 641 | in2="SourceGraphic" | |
| 642 | in="flood" /> | |
| 643 | <feGaussianBlur | |
| 644 | id="feGaussianBlur2980" | |
| 645 | result="blur" | |
| 646 | stdDeviation="2" | |
| 647 | in="composite1" /> | |
| 648 | <feOffset | |
| 649 | id="feOffset2982" | |
| 650 | result="offset" | |
| 651 | dy="3" | |
| 652 | dx="3" /> | |
| 653 | <feComposite | |
| 654 | id="feComposite2984" | |
| 655 | result="composite2" | |
| 656 | operator="over" | |
| 657 | in2="offset" | |
| 658 | in="SourceGraphic" /> | |
| 659 | </filter> | |
| 660 | </defs> | |
| 661 | <sodipodi:namedview | |
| 662 | inkscape:snap-text-baseline="false" | |
| 663 | inkscape:document-rotation="0" | |
| 664 | fit-margin-bottom="20" | |
| 665 | fit-margin-right="20" | |
| 666 | fit-margin-left="20" | |
| 667 | fit-margin-top="20" | |
| 668 | inkscape:current-layer="svg4610" | |
| 669 | inkscape:cy="370.55742" | |
| 670 | inkscape:cx="398.61418" | |
| 671 | inkscape:zoom="1.3753763" | |
| 672 | showgrid="false" | |
| 673 | id="namedview4612" | |
| 674 | inkscape:window-height="1280" | |
| 675 | inkscape:window-width="2055" | |
| 676 | inkscape:pageshadow="2" | |
| 677 | inkscape:pageopacity="1" | |
| 678 | guidetolerance="10" | |
| 679 | gridtolerance="10" | |
| 680 | objecttolerance="10" | |
| 681 | borderopacity="1" | |
| 682 | bordercolor="#666666" | |
| 683 | pagecolor="#ffffff" | |
| 684 | inkscape:window-x="215" | |
| 685 | inkscape:window-y="26" | |
| 686 | inkscape:window-maximized="0" /> | |
| 687 | <path | |
| 688 | sodipodi:nodetypes="ccssssc" | |
| 689 | inkscape:connector-curvature="0" | |
| 690 | style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 691 | d="M 53.35547,445.11522 V 790.96744 H 741.0332 c 1.6112,0 2.90821,-1.29701 2.90821,-2.9082 V 448.02342 c 0,-1.6112 -1.297,-2.9082 -2.90821,-2.9082 z" | |
| 692 | id="path9961" /> | |
| 693 | <path | |
| 694 | sodipodi:nodetypes="sssccssss" | |
| 695 | id="path9940" | |
| 696 | d="m 20.5,787.82486 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 445.11522 H 23.64062 c -0.87012,0 -1.65487,0.35019 -2.22265,0.91797 -0.56778,0.56778 -0.91797,1.35254 -0.91797,2.22266 z" | |
| 697 | style="fill:#df4d65;fill-opacity:1;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 698 | inkscape:connector-curvature="0" /> | |
| 699 | <path | |
| 700 | sodipodi:nodetypes="sssccssss" | |
| 701 | id="path11125" | |
| 702 | d="m 20.5,423.31014 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.354494,0.9764 2.22265,0.91797 H 53.35547 V 210.6005 H 23.64062 c -0.87012,0 -1.65487,0.3502 -2.22265,0.918 C 20.85019,212.08629 20.5,212.871 20.5,213.74109 Z" | |
| 703 | style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 704 | inkscape:connector-curvature="0" /> | |
| 705 | <path | |
| 706 | sodipodi:nodetypes="ccssssc" | |
| 707 | inkscape:connector-curvature="0" | |
| 708 | style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 709 | d="m 53.35547,210.6005 v 215.85222 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 213.5087 c 0,-1.6112 -1.29701,-2.90352 -2.9082,-2.9082 z" | |
| 710 | id="path11123" /> | |
| 711 | <path | |
| 712 | id="path6150" | |
| 713 | d="m 557.756,222.53493 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 714 | style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)" | |
| 715 | inkscape:connector-curvature="0" | |
| 716 | sodipodi:nodetypes="sssccssss" /> | |
| 717 | <path | |
| 718 | sodipodi:nodetypes="ccssssc" | |
| 719 | id="path6134" | |
| 720 | d="m 720.75716,255.39041 h -165.6211 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 721 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)" | |
| 722 | inkscape:connector-curvature="0" /> | |
| 723 | <path | |
| 724 | id="path6082" | |
| 725 | d="m 317.13559,222.53494 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 726 | style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)" | |
| 727 | inkscape:connector-curvature="0" | |
| 728 | sodipodi:nodetypes="sssccssss" /> | |
| 729 | <path | |
| 730 | sodipodi:nodetypes="ccssssc" | |
| 731 | id="path6080" | |
| 732 | d="M 479.61412,255.39041 H 313.99301 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z" | |
| 733 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)" | |
| 734 | inkscape:connector-curvature="0" /> | |
| 735 | <path | |
| 736 | id="path10980" | |
| 737 | d="M 53.35547,20.500012 V 188.35224 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 23.408212 c 0,-1.6112 -1.29701,-2.912886 -2.9082,-2.9082 z" | |
| 738 | style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 739 | inkscape:connector-curvature="0" | |
| 740 | sodipodi:nodetypes="ccssssc" /> | |
| 741 | <path | |
| 742 | inkscape:connector-curvature="0" | |
| 743 | style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 744 | d="m 20.5,185.20966 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 20.500012 H 23.64062 c -0.87012,0 -1.65487,0.350201 -2.22265,0.918 -0.56778,0.5678 -0.91797,1.3525 -0.91797,2.2226 z" | |
| 745 | id="path10982" | |
| 746 | sodipodi:nodetypes="sssccssss" /> | |
| 747 | <path | |
| 748 | sodipodi:nodetypes="sssccssss" | |
| 749 | inkscape:connector-curvature="0" | |
| 750 | style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)" | |
| 751 | d="m 557.75599,36.704447 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 752 | id="path4857" /> | |
| 753 | <path | |
| 754 | inkscape:connector-curvature="0" | |
| 755 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)" | |
| 756 | d="M 720.23451,69.559917 H 554.61341 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 757 | id="path4853" /> | |
| 758 | <path | |
| 759 | sodipodi:nodetypes="sssccssss" | |
| 760 | inkscape:connector-curvature="0" | |
| 761 | style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)" | |
| 762 | d="m 317.13558,36.704447 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 763 | id="path5726" /> | |
| 764 | <path | |
| 765 | inkscape:connector-curvature="0" | |
| 766 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)" | |
| 767 | d="M 479.61411,69.559917 H 313.993 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 476.7059 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z" | |
| 768 | id="path5724" /> | |
| 769 | <path | |
| 770 | id="path4721" | |
| 771 | d="m 235.85308,44.704447 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 772 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 773 | inkscape:connector-curvature="0" /> | |
| 774 | <path | |
| 775 | sodipodi:nodetypes="sssccssss" | |
| 776 | id="path4719" | |
| 777 | d="m 76.515197,36.704447 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 H 238.99371 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 778 | style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)" | |
| 779 | inkscape:connector-curvature="0" /> | |
| 780 | <path | |
| 781 | id="path4723" | |
| 782 | d="M 238.99372,69.559917 H 73.372613 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 236.08552 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 783 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)" | |
| 784 | inkscape:connector-curvature="0" /> | |
| 785 | <path | |
| 786 | id="rect4622" | |
| 787 | d="m 76.280822,44.704447 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z" | |
| 788 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 789 | inkscape:connector-curvature="0" /> | |
| 790 | <path | |
| 791 | sodipodi:nodetypes="cc" | |
| 792 | inkscape:connector-curvature="0" | |
| 793 | id="path9889" | |
| 794 | d="m 397.61301,500.62068 -0.50618,32.59418" | |
| 795 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:1.9694221;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9893)" /> | |
| 796 | <path | |
| 797 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9715)" | |
| 798 | d="m 554.61351,648.83688 -69.6817,47.69253" | |
| 799 | id="path9711" | |
| 800 | inkscape:connector-curvature="0" | |
| 801 | sodipodi:nodetypes="cc" /> | |
| 802 | <path | |
| 803 | sodipodi:nodetypes="cc" | |
| 804 | inkscape:connector-curvature="0" | |
| 805 | id="path9675" | |
| 806 | d="M 554.61351,567.95047 484.93181,615.643" | |
| 807 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9679)" /> | |
| 808 | <rect | |
| 809 | ry="3.9839513" | |
| 810 | rx="3.9205718" | |
| 811 | y="537.09552" | |
| 812 | x="554.61353" | |
| 813 | height="32.855" | |
| 814 | width="165.621" | |
| 815 | id="rect9618" | |
| 816 | style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2842)" /> | |
| 817 | <rect | |
| 818 | ry="3.9839513" | |
| 819 | rx="3.9205718" | |
| 820 | y="537.09552" | |
| 821 | x="73.372665" | |
| 822 | height="32.855" | |
| 823 | width="165.621" | |
| 824 | id="rect9614" | |
| 825 | style="opacity:1;fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2914)" /> | |
| 826 | <path | |
| 827 | inkscape:connector-curvature="0" | |
| 828 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 829 | d="m 235.85308,545.09525 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 830 | id="path9323" /> | |
| 831 | <path | |
| 832 | inkscape:connector-curvature="0" | |
| 833 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 834 | d="m 76.280823,545.09525 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z" | |
| 835 | id="path9327" /> | |
| 836 | <rect | |
| 837 | style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2902)" | |
| 838 | id="rect9616" | |
| 839 | width="165.621" | |
| 840 | height="32.855" | |
| 841 | x="313.99307" | |
| 842 | y="537.09552" | |
| 843 | rx="3.9205718" | |
| 844 | ry="3.9839513" /> | |
| 845 | <path | |
| 846 | sodipodi:nodetypes="cc" | |
| 847 | inkscape:connector-curvature="0" | |
| 848 | id="path9491" | |
| 849 | d="m 240.99257,554.11276 65.23376,-1.01307" | |
| 850 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9513)" /> | |
| 851 | <path | |
| 852 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9640)" | |
| 853 | d="m 481.61298,554.11276 65.23376,-1.01307" | |
| 854 | id="path9501" | |
| 855 | inkscape:connector-curvature="0" | |
| 856 | sodipodi:nodetypes="cc" /> | |
| 857 | <rect | |
| 858 | ry="3.9839513" | |
| 859 | rx="3.9205718" | |
| 860 | y="617.79578" | |
| 861 | x="313.99307" | |
| 862 | height="32.855" | |
| 863 | width="165.621" | |
| 864 | id="rect9620" | |
| 865 | style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2890)" /> | |
| 866 | <path | |
| 867 | sodipodi:nodetypes="cc" | |
| 868 | inkscape:connector-curvature="0" | |
| 869 | id="path9681" | |
| 870 | d="m 481.61298,634.81299 65.23376,-1.01307" | |
| 871 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9685)" /> | |
| 872 | <rect | |
| 873 | style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2854)" | |
| 874 | id="rect9687" | |
| 875 | width="165.621" | |
| 876 | height="32.855" | |
| 877 | x="554.61353" | |
| 878 | y="617.79578" | |
| 879 | rx="3.9205718" | |
| 880 | ry="3.9839513" /> | |
| 881 | <path | |
| 882 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9750)" | |
| 883 | d="m 481.61298,715.51321 65.23376,-1.01307" | |
| 884 | id="path9734" | |
| 885 | inkscape:connector-curvature="0" | |
| 886 | sodipodi:nodetypes="cc" /> | |
| 887 | <rect | |
| 888 | ry="3.9839513" | |
| 889 | rx="3.9205718" | |
| 890 | y="698.49591" | |
| 891 | x="554.61353" | |
| 892 | height="32.855" | |
| 893 | width="165.621" | |
| 894 | id="rect9736" | |
| 895 | style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2866)" /> | |
| 896 | <path | |
| 897 | id="path9830" | |
| 898 | d="m 356.40451,489.45323 c -0.80426,0 -1.45167,0.64741 -1.45167,1.45166 v 0.11602 c 0,-0.43433 0.1748,-0.82605 0.45822,-1.10946 0.28341,-0.28342 0.6761,-0.45822 1.11043,-0.45822 z" | |
| 899 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10902636;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 900 | inkscape:connector-curvature="0" /> | |
| 901 | <rect | |
| 902 | style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.9391377;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 903 | id="rect9826" | |
| 904 | width="120.98324" | |
| 905 | height="24" | |
| 906 | x="336.82672" | |
| 907 | y="477.86002" | |
| 908 | rx="2.8639088" | |
| 909 | ry="2.9102066" /> | |
| 910 | <path | |
| 911 | id="path10514" | |
| 912 | d="m 235.85301,637.23875 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 913 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 914 | inkscape:connector-curvature="0" /> | |
| 915 | <rect | |
| 916 | style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2878)" | |
| 917 | id="rect9717" | |
| 918 | width="165.621" | |
| 919 | height="32.855" | |
| 920 | x="313.99307" | |
| 921 | y="698.49591" | |
| 922 | rx="3.9205718" | |
| 923 | ry="3.9839513" /> | |
| 924 | <path | |
| 925 | id="path10537" | |
| 926 | d="M 238.99366,636.97465 H 73.372671 V 729.175 c 0,1.2055 0.970418,2.17592 2.175911,2.17592 H 236.81776 c 1.20549,0 2.1759,-0.97042 2.1759,-2.17592 z" | |
| 927 | style="opacity:1;fill:#333333;fill-opacity:0.93333333;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 928 | inkscape:connector-curvature="0" | |
| 929 | sodipodi:nodetypes="ccssssc" /> | |
| 930 | <path | |
| 931 | sodipodi:nodetypes="sssccssss" | |
| 932 | id="path10516" | |
| 933 | d="m 75.723937,612.39226 c -0.651025,0 -1.239637,0.26201 -1.664447,0.68682 -0.424811,0.42482 -0.686822,1.01196 -0.686822,1.66299 v 22.23258 H 238.99366 v -22.23258 c 0,-0.65103 -0.26201,-1.23817 -0.68682,-1.66299 -0.42481,-0.42481 -1.01197,-0.68682 -1.66299,-0.68682 z" | |
| 934 | style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 935 | inkscape:connector-curvature="0" /> | |
| 936 | <path | |
| 937 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker10933)" | |
| 938 | d="m 240.99257,715.51318 65.23376,-1.01307" | |
| 939 | id="path10929" | |
| 940 | inkscape:connector-curvature="0" | |
| 941 | sodipodi:nodetypes="cc" /> | |
| 942 | <path | |
| 943 | style="fill:#df4d65;fill-opacity:1;fill-rule:evenodd;stroke-width:0.05250736" | |
| 944 | d="m 399.47357,99.155037 -0.12716,0.0127 -0.12716,0.0141 -0.12574,0.0141 -0.12716,0.0155 -0.61883,0.967813 -0.29106,0.9325 -0.17943,0.91977 -0.1215,1.09073 -0.30943,0.0636 -0.308,0.0678 -0.308,0.0735 -0.30801,0.0763 -0.58069,-0.93108 -0.5609,-0.75023 -0.66687,-0.71349 -0.97912,-0.6033 -0.12009,0.0409 -0.1201,0.0424 -0.12009,0.0424 -0.12009,0.0424 -0.38854,1.08085 -0.0763,0.97487 0.0297,0.93673 0.12575,1.08932 -0.28823,0.1314 -0.28681,0.13563 -0.28398,0.13987 -0.28117,0.14271 -0.77425,-0.77849 -0.7149,-0.60612 -0.80816,-0.54819 -1.0879,-0.37017 -0.10879,0.0678 -0.10738,0.0679 -0.10738,0.0678 -0.10738,0.0678 -0.13846,1.14018 0.14129,0.9664 0.23735,0.90706 0.36594,1.03563 -0.25149,0.19215 -0.24865,0.19497 -0.24585,0.19922 -0.24442,0.20204 -0.92825,-0.58634 -0.83077,-0.43234 -0.90988,-0.35463 -1.14301,-0.11868 -0.0904,0.0904 -0.0904,0.089 -0.089,0.0904 -0.0904,0.0904 0.11868,1.14301 0.35462,0.90988 0.43234,0.83218 0.58634,0.92684 -0.20204,0.24442 -0.19921,0.24584 -0.19498,0.24867 -0.19215,0.25148 -1.03563,-0.36593 -0.90705,-0.23736 -0.96641,-0.14128 -1.14018,0.13845 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10879 0.37016,1.0879 0.5482,0.80816 0.60612,0.71491 0.77848,0.77425 -0.1427,0.28117 -0.13987,0.28398 -0.13564,0.28681 -0.13139,0.28823 -1.09073,-0.12575 -0.93532,-0.0297 -0.97487,0.0763 -1.08084,0.38854 -0.0424,0.12009 -0.0424,0.12009 -0.0424,0.1201 -0.0409,0.12009 0.6033,0.97912 0.7135,0.66686 0.75023,0.56091 0.93107,0.58069 -0.0763,0.30801 -0.0735,0.308 -0.0678,0.308 -0.0636,0.30942 -1.09073,0.1215 -0.91977,0.17944 -0.9325,0.29105 -0.96781,0.61883 -0.0156,0.12717 -0.0141,0.12574 -0.0141,0.12716 -0.0127,0.12716 0.80533,0.81804 0.84348,0.49168 0.85619,0.38006 1.03704,0.36028 -0.006,0.31648 -0.003,0.31648 0.003,0.31648 0.006,0.31648 -1.03704,0.35887 -0.85619,0.38006 -0.84348,0.49168 -0.80533,0.81946 0.0127,0.12716 0.0141,0.12574 0.0141,0.12716 0.0156,0.12574 0.96781,0.62026 0.9325,0.29104 0.91977,0.17944 1.09073,0.12009 0.0636,0.30942 0.0678,0.30941 0.0735,0.30801 0.0763,0.30659 -0.93107,0.5821 -0.75023,0.56091 -0.7135,0.66687 -0.6033,0.97771 0.0409,0.12008 0.0424,0.12151 0.0424,0.1201 0.0424,0.11868 1.08084,0.38995 0.97487,0.0763 0.93532,-0.0297 1.09073,-0.12574 0.13139,0.28822 0.13564,0.2854 0.13987,0.28399 0.1427,0.28257 -0.77848,0.77425 -0.60612,0.7135 -0.5482,0.80957 -0.37016,1.08791 0.0678,0.10737 0.0678,0.10879 0.0678,0.10738 0.0678,0.10738 1.14018,0.13846 0.96641,-0.1427 0.90705,-0.23736 1.03563,-0.36452 0.19215,0.25149 0.19498,0.24866 0.19921,0.24584 0.20204,0.24302 -0.58634,0.92825 -0.43234,0.83076 -0.35462,0.9113 -0.11868,1.14159 0.0904,0.0918 0.089,0.089 0.0904,0.0904 0.0904,0.089 1.14301,-0.11868 0.90988,-0.35321 0.83077,-0.43375 0.92825,-0.58493 0.24442,0.20204 0.24585,0.19921 0.24865,0.19497 0.25149,0.19216 -0.36594,1.03563 -0.23735,0.90564 -0.14129,0.9664 0.13846,1.14018 0.10738,0.0692 0.10738,0.0678 0.10738,0.0678 0.10879,0.0664 1.0879,-0.37017 0.80816,-0.54677 0.7149,-0.60754 0.77425,-0.77708 0.28117,0.14271 0.28398,0.13987 0.28681,0.13422 0.28823,0.13139 -0.12575,1.09074 -0.0297,0.93673 0.0763,0.97346 0.38854,1.08084 0.12009,0.0438 0.12009,0.0424 0.1201,0.041 0.12009,0.0409 0.97912,-0.60188 0.66687,-0.71349 0.5609,-0.75165 0.58069,-0.93108 0.30801,0.0777 0.308,0.072 0.308,0.0692 0.30943,0.0636 0.1215,1.09073 0.17943,0.91978 0.29106,0.93249 0.61883,0.9664 0.12716,0.0156 0.12574,0.0141 0.12716,0.0141 0.12716,0.0141 0.81806,-0.80533 0.49167,-0.8449 0.38006,-0.85619 0.36028,-1.03704 0.31648,0.007 0.31648,0.003 0.31649,-0.003 0.31648,-0.007 0.36028,1.03704 0.37865,0.85619 0.49167,0.8449 0.81947,0.80533 0.12715,-0.0141 0.12574,-0.0141 0.12717,-0.0141 0.12574,-0.0156 0.62025,-0.9664 0.29104,-0.93249 0.17944,-0.91978 0.12009,-1.09073 0.30942,-0.0636 0.30942,-0.0692 0.30799,-0.072 0.3066,-0.0777 0.58211,0.93108 0.5609,0.75165 0.66687,0.71349 0.97771,0.60188 0.12009,-0.0409 0.1215,-0.041 0.1201,-0.0424 0.11868,-0.0438 0.38995,-1.08084 0.0763,-0.97346 -0.0297,-0.93673 -0.12574,-1.09074 0.28822,-0.13139 0.2854,-0.13422 0.28398,-0.13987 0.28258,-0.14271 0.77424,0.77708 0.7135,0.60754 0.80957,0.54677 1.08791,0.37017 0.10737,-0.0664 0.10879,-0.0678 0.10738,-0.0678 0.10738,-0.0692 0.13847,-1.14018 -0.14271,-0.9664 -0.23737,-0.90564 -0.36452,-1.03563 0.25149,-0.19216 0.24866,-0.19497 0.24585,-0.19921 0.24301,-0.20204 0.92825,0.58493 0.83077,0.43375 0.91129,0.35321 1.1416,0.11868 0.0904,-0.089 0.0904,-0.0904 0.0904,-0.089 0.089,-0.0918 -0.11868,-1.14159 -0.35321,-0.9113 -0.43375,-0.83076 -0.58492,-0.92825 0.20203,-0.24302 0.19921,-0.24584 0.19498,-0.24866 0.19215,-0.25149 1.03563,0.36452 0.90564,0.23736 0.9664,0.1427 1.14018,-0.13846 0.0692,-0.10738 0.0678,-0.10738 0.0678,-0.10879 0.0664,-0.10737 -0.37017,-1.08791 -0.54677,-0.80957 -0.60754,-0.7135 -0.77706,-0.77425 0.1427,-0.28257 0.13986,-0.28399 0.13423,-0.2854 0.13139,-0.28822 1.09073,0.12574 0.93674,0.0297 0.97345,-0.0763 1.08085,-0.38995 0.0438,-0.11868 0.0424,-0.1201 0.0409,-0.12151 0.041,-0.12008 -0.6019,-0.97771 -0.71349,-0.66687 -0.75164,-0.56091 -0.93108,-0.5821 0.0777,-0.30659 0.072,-0.30801 0.0692,-0.30941 0.0636,-0.30942 1.09073,-0.12009 0.91978,-0.17944 0.93249,-0.29104 0.9664,-0.62026 0.0155,-0.12574 0.0141,-0.12716 0.0141,-0.12574 0.0141,-0.12716 -0.80533,-0.81946 -0.8449,-0.49168 -0.85619,-0.38006 -1.03704,-0.35887 0.007,-0.31648 0.003,-0.31648 -0.003,-0.31648 -0.007,-0.31648 1.03704,-0.36028 0.85619,-0.38006 0.8449,-0.49168 0.80533,-0.81804 -0.0141,-0.12716 -0.0141,-0.12716 -0.0141,-0.12574 -0.0155,-0.12717 -0.9664,-0.61883 -0.93249,-0.29105 -0.91978,-0.17944 -1.09073,-0.1215 -0.0636,-0.30942 -0.0692,-0.308 -0.072,-0.308 -0.0777,-0.30801 0.93108,-0.58069 0.75164,-0.56091 0.71349,-0.66686 0.6019,-0.97912 -0.041,-0.12009 -0.0409,-0.1201 -0.0424,-0.12009 -0.0438,-0.12009 -1.08085,-0.38854 -0.97345,-0.0763 -0.93674,0.0297 -1.09073,0.12575 -0.13139,-0.28823 -0.13423,-0.28681 -0.13986,-0.28398 -0.1427,-0.28117 0.77706,-0.77425 0.60754,-0.71491 0.54677,-0.80816 0.37017,-1.0879 -0.0664,-0.10879 -0.0678,-0.10738 -0.0678,-0.10738 -0.0692,-0.10738 -1.14018,-0.13845 -0.9664,0.14128 -0.90564,0.23736 -1.03563,0.36593 -0.19215,-0.25148 -0.19498,-0.24867 -0.19921,-0.24584 -0.20203,-0.24442 0.58492,-0.92684 0.43375,-0.83218 0.35321,-0.90988 0.11868,-1.14301 -0.089,-0.0904 -0.0904,-0.0904 -0.0904,-0.089 -0.0904,-0.0904 -1.1416,0.11868 -0.91129,0.35463 -0.83077,0.43234 -0.92825,0.58634 -0.24301,-0.20204 -0.24585,-0.19922 -0.24866,-0.19497 -0.25149,-0.19215 0.36452,-1.03563 0.23737,-0.90706 0.14271,-0.9664 -0.13847,-1.14018 -0.10738,-0.0678 -0.10738,-0.0678 -0.10879,-0.0679 -0.10737,-0.0678 -1.08791,0.37017 -0.80957,0.54819 -0.7135,0.60612 -0.77424,0.77849 -0.28258,-0.14271 -0.28398,-0.13987 -0.2854,-0.13563 -0.28822,-0.1314 0.12574,-1.08932 0.0297,-0.93673 -0.0763,-0.97487 -0.38995,-1.08085 -0.11868,-0.0424 -0.1201,-0.0424 -0.1215,-0.0424 -0.12009,-0.0409 -0.97771,0.6033 -0.66687,0.71349 -0.5609,0.75023 -0.58211,0.93108 -0.3066,-0.0763 -0.30799,-0.0735 -0.30942,-0.0678 -0.30942,-0.0636 -0.12009,-1.09073 -0.17944,-0.91977 -0.29104,-0.9325 -0.62025,-0.967813 -0.12574,-0.0155 -0.12717,-0.0141 -0.12574,-0.0141 -0.12715,-0.0127 -0.81947,0.80533 -0.49167,0.843483 -0.37865,0.8562 -0.36028,1.03704 -0.31648,-0.006 -0.31649,-0.003 -0.31648,0.003 -0.31648,0.006 -0.36028,-1.03704 -0.38006,-0.8562 -0.49167,-0.843483 z m 2.68302,20.688573 a 5.3990039,5.3990039 0 0 1 5.39856,5.39997 5.3990039,5.3990039 0 0 1 -5.39856,5.39855 5.3990039,5.3990039 0 0 1 -5.39996,-5.39855 5.3990039,5.3990039 0 0 1 5.39996,-5.39997 z" | |
| 945 | id="path5693" | |
| 946 | inkscape:connector-curvature="0" /> | |
| 947 | <path | |
| 948 | inkscape:connector-curvature="0" | |
| 949 | d="m 380.9529,101.31918 a 4.37599,4.37599 0 0 1 -4.37599,4.37599 4.37599,4.37599 0 0 1 -4.37599,-4.37599 4.37599,4.37599 0 0 1 4.37599,-4.375983 4.37599,4.37599 0 0 1 4.37599,4.375983 z m 4.63493,-1.27213 c -0.32212,-0.118873 -0.95326,0.0926 -0.92258,-0.401293 -0.13877,-0.39635 -0.21401,-0.74537 0.27363,-0.88946 0.78055,-0.47633 1.45123,-1.16128 1.74461,-2.04171 0.15411,-0.39145 -0.3432,-0.48754 -0.63657,-0.53536 -0.91614,-0.25589 -1.86519,0.0578 -2.73328,0.35995 -0.11023,-0.31345 -0.69059,-0.56868 -0.47901,-0.88657 0.56405,-0.84324 0.99162,-1.8335 0.85012,-2.86709 -0.031,-0.41955 -0.52073,-0.29038 -0.8058,-0.20618 -0.93646,0.16692 -1.65537,0.86143 -2.30642,1.51029 -0.23528,-0.23464 -0.86896,-0.21274 -0.81624,-0.59097 0.14241,-1.00446 0.0978,-2.08217 -0.47806,-2.95201 -0.20995,-0.36459 -0.59514,-0.0357 -0.81544,0.16385 -0.7713,0.55671 -1.11768,1.49434 -1.42274,2.36143 -0.3138,-0.10927 -0.87519,0.18536 -0.99181,-0.17826 -0.30757,-0.96675 -0.81528,-1.91841 -1.71153,-2.45229 -0.34734,-0.23737 -0.55176,0.22606 -0.66364,0.50149 -0.45331,0.83622 -0.35865,1.83127 -0.25723,2.74482 -0.33012,0.0378 -0.70817,0.54672 -0.97098,0.26977 -0.69651,-0.73763 -1.56687,-1.37476 -2.60601,-1.46682 -0.41593,-0.0631 -0.39904,0.443 -0.38034,0.73971 -0.0456,0.95009 0.47143,1.80554 0.95918,2.58463 -0.28104,0.17725 -0.40076,0.79984 -0.75777,0.6643 -0.94759,-0.36236 -2.00818,-0.55864 -2.98437,-0.1908 -0.40215,0.12357 -0.16723,0.57227 -0.0217,0.83147 0.37114,0.8758 1.2081,1.42221 1.98561,1.91248 -0.17632,0.28163 -0.0141,0.89453 -0.39449,0.92732 -1.01097,0.0847 -2.05173,0.367983 -2.77166,1.122963 -0.3087,0.2858 0.0976,0.58816 0.34122,0.75853 0.71439,0.62804 1.70558,0.75716 2.6188,0.8616 -0.0367,0.33025 0.37548,0.81204 0.0469,1.00665 -0.87415,0.51488 -1.68886,1.22174 -2.00994,2.2143 -0.15411,0.39144 0.3432,0.48753 0.63657,0.53535 0.91612,0.25589 1.86516,-0.0578 2.73323,-0.35995 0.11029,0.31341 0.69063,0.56869 0.47905,0.88657 -0.56412,0.8432 -0.99155,1.83352 -0.85015,2.86709 0.031,0.41952 0.52075,0.2904 0.80584,0.20618 0.93644,-0.16692 1.65537,-0.86139 2.30637,-1.51029 0.2353,0.23464 0.86901,0.21272 0.81629,0.59098 -0.14241,1.00446 -0.0978,2.08215 0.47802,2.95202 0.20997,0.36455 0.59517,0.0357 0.81548,-0.16387 0.77125,-0.55674 1.11768,-1.49435 1.42274,-2.36142 0.31379,0.10926 0.8752,-0.18537 0.99181,0.17824 0.30754,0.96678 0.81527,1.91842 1.71153,2.45229 0.34733,0.23738 0.55172,-0.22608 0.66362,-0.50146 0.45335,-0.83621 0.35866,-1.83128 0.25725,-2.74484 0.33011,-0.0378 0.70812,-0.54672 0.97093,-0.26977 0.69656,0.7376 1.5669,1.37477 2.60606,1.46683 0.41593,0.0632 0.39897,-0.44304 0.38032,-0.73972 0.0457,-0.95011 -0.4715,-1.8055 -0.95916,-2.58463 0.28105,-0.17722 0.40074,-0.79983 0.75772,-0.6643 0.94761,0.36234 2.00821,0.55865 2.98442,0.1908 0.40215,-0.12357 0.16723,-0.57228 0.0217,-0.83146 -0.37116,-0.87579 -1.20814,-1.42218 -1.98561,-1.91249 0.17632,-0.28163 0.0141,-0.89453 0.39449,-0.92732 1.01097,-0.0847 2.05173,-0.36799 2.77166,-1.12295 0.3087,-0.28581 -0.0976,-0.58817 -0.34122,-0.75854 -0.47483,-0.43652 -1.13407,-0.61787 -1.75144,-0.75008 z" | |
| 950 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke-width:0.04014921" | |
| 951 | id="path5687" /> | |
| 952 | <path | |
| 953 | style="fill:#05556e;fill-opacity:1;stroke-width:0.11881336" | |
| 954 | id="path4816" | |
| 955 | d="m 619.92265,90.37586 h 2.72717 v 2.71445 h 2.51374 v -2.71445 h 2.72716 v 8.21477 h -2.72716 v -2.73825 h -2.49003 v 2.73825 h -2.75088 m 11.57268,-5.47651 h -2.40702 v -2.73826 h 7.55307 v 2.73826 h -2.41888 v 5.47651 h -2.72717 m 6.34363,-8.21477 h 2.8576 l 1.75487,2.89303 1.75487,-2.89303 h 2.8576 v 8.21477 h -2.72717 v -4.07167 l -1.90901,2.95256 -1.90902,-2.95256 v 4.07167 h -2.67974 m 10.57667,-8.21477 h 2.72717 v 5.50033 h 3.86546 v 2.71444 h -6.59263" | |
| 956 | inkscape:connector-curvature="0" /> | |
| 957 | <path | |
| 958 | id="path4818" | |
| 959 | d="m 619.82779,146.45062 -3.91289,-44.09786 h 43.01811 l -3.91289,44.07405 -17.63174,4.90505" | |
| 960 | inkscape:connector-curvature="0" | |
| 961 | style="fill:#e44d26;stroke-width:0.11881336" /> | |
| 962 | <path | |
| 963 | id="path4820" | |
| 964 | d="m 637.42396,147.58164 v -41.60962 h 17.5843 l -3.3556,37.62129" | |
| 965 | inkscape:connector-curvature="0" | |
| 966 | style="fill:#f16529;stroke-width:0.11881336" /> | |
| 967 | <path | |
| 968 | id="path4822" | |
| 969 | d="m 623.90669,111.3652 h 13.51727 v 5.40508 h -7.61236 l 0.498,5.53605 h 7.11436 v 5.39318 h -12.04697 m 0.23714,2.71444 h 5.40691 l 0.37943,4.32169 6.02349,1.61914 v 5.64319 L 626.373,138.90255" | |
| 970 | inkscape:connector-curvature="0" | |
| 971 | style="fill:#ebebeb;stroke-width:0.11881336" /> | |
| 972 | <path | |
| 973 | id="path4824" | |
| 974 | d="m 650.89379,111.3652 h -13.49355 v 5.40508 h 12.99555 m -0.48615,5.53605 h -12.5094 v 5.40508 h 6.64006 l -0.62843,7.02423 -6.01163,1.61914 v 5.61938 l 11.02724,-3.07161" | |
| 975 | inkscape:connector-curvature="0" | |
| 976 | style="fill:#ffffff;stroke-width:0.11881336" /> | |
| 977 | <path | |
| 978 | sodipodi:nodetypes="cc" | |
| 979 | inkscape:connector-curvature="0" | |
| 980 | id="path5804" | |
| 981 | d="m 240.99252,105.07517 65.2338,-1.01308" | |
| 982 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9479)" /> | |
| 983 | <path | |
| 984 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9505)" | |
| 985 | d="m 481.61302,105.07517 65.2337,-1.01308" | |
| 986 | id="path9497" | |
| 987 | inkscape:connector-curvature="0" | |
| 988 | sodipodi:nodetypes="cc" /> | |
| 989 | <path | |
| 990 | inkscape:connector-curvature="0" | |
| 991 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 992 | d="m 235.85308,230.53494 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 993 | id="path6102" /> | |
| 994 | <path | |
| 995 | inkscape:connector-curvature="0" | |
| 996 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 997 | d="m 76.280823,230.53494 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z" | |
| 998 | id="path6106" /> | |
| 999 | <path | |
| 1000 | inkscape:connector-curvature="0" | |
| 1001 | style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)" | |
| 1002 | d="m 76.515198,222.53494 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 h 165.62109 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 1003 | id="path6104" | |
| 1004 | sodipodi:nodetypes="sssccssss" /> | |
| 1005 | <path | |
| 1006 | sodipodi:nodetypes="ccssssc" | |
| 1007 | inkscape:connector-curvature="0" | |
| 1008 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)" | |
| 1009 | d="M 238.99371,255.39041 H 73.37262 v 152.63392 c 0,1.6112 1.297008,2.90821 2.908203,2.90821 H 236.08551 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 1010 | id="path6100" /> | |
| 1011 | <path | |
| 1012 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9509)" | |
| 1013 | d="m 240.99257,328.95043 65.23376,-1.01307" | |
| 1014 | id="path9485" | |
| 1015 | inkscape:connector-curvature="0" | |
| 1016 | sodipodi:nodetypes="cc" /> | |
| 1017 | <path | |
| 1018 | sodipodi:nodetypes="cc" | |
| 1019 | inkscape:connector-curvature="0" | |
| 1020 | id="path9757" | |
| 1021 | d="m 481.61298,300.08996 65.23376,-1.01307" | |
| 1022 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9761)" /> | |
| 1023 | <path | |
| 1024 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9767)" | |
| 1025 | d="M 552.61456,372.04139 487.3808,371.02832" | |
| 1026 | id="path9763" | |
| 1027 | inkscape:connector-curvature="0" | |
| 1028 | sodipodi:nodetypes="cc" /> | |
| 1029 | <text | |
| 1030 | id="text2269" | |
| 1031 | y="62.149761" | |
| 1032 | x="115.43707" | |
| 1033 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1034 | xml:space="preserve"><tspan | |
| 1035 | y="62.149761" | |
| 1036 | x="115.43707" | |
| 1037 | id="tspan2267" | |
| 1038 | sodipodi:role="line">Text Edit</tspan></text> | |
| 1039 | <text | |
| 1040 | transform="rotate(-90)" | |
| 1041 | id="text2273" | |
| 1042 | y="43.507812" | |
| 1043 | x="-132.24059" | |
| 1044 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1045 | xml:space="preserve"><tspan | |
| 1046 | y="43.507812" | |
| 1047 | x="-132.24059" | |
| 1048 | id="tspan2271" | |
| 1049 | sodipodi:role="line">Today</tspan></text> | |
| 1050 | <text | |
| 1051 | id="text2277" | |
| 1052 | y="61.540386" | |
| 1053 | x="358.88168" | |
| 1054 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1055 | xml:space="preserve"><tspan | |
| 1056 | y="61.540386" | |
| 1057 | x="358.88168" | |
| 1058 | id="tspan2275" | |
| 1059 | sodipodi:role="line">Process</tspan></text> | |
| 1060 | <text | |
| 1061 | id="text2281" | |
| 1062 | y="59.34898" | |
| 1063 | x="605.30872" | |
| 1064 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1065 | xml:space="preserve"><tspan | |
| 1066 | y="59.34898" | |
| 1067 | x="605.30872" | |
| 1068 | id="tspan2279" | |
| 1069 | sodipodi:role="line">Output</tspan></text> | |
| 1070 | <text | |
| 1071 | id="text2285" | |
| 1072 | y="245.17946" | |
| 1073 | x="605.30872" | |
| 1074 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1075 | xml:space="preserve"><tspan | |
| 1076 | y="245.17946" | |
| 1077 | x="605.30872" | |
| 1078 | id="tspan2283" | |
| 1079 | sodipodi:role="line">Output</tspan></text> | |
| 1080 | <text | |
| 1081 | id="text2289" | |
| 1082 | y="247.37088" | |
| 1083 | x="358.88168" | |
| 1084 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1085 | xml:space="preserve"><tspan | |
| 1086 | y="247.37088" | |
| 1087 | x="358.88168" | |
| 1088 | id="tspan2287" | |
| 1089 | sodipodi:role="line">Process</tspan></text> | |
| 1090 | <text | |
| 1091 | id="text2293" | |
| 1092 | y="247.98026" | |
| 1093 | x="115.43707" | |
| 1094 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1095 | xml:space="preserve"><tspan | |
| 1096 | y="247.98026" | |
| 1097 | x="115.43707" | |
| 1098 | id="tspan2291" | |
| 1099 | sodipodi:role="line">Text Edit</tspan></text> | |
| 1100 | <text | |
| 1101 | transform="rotate(-90)" | |
| 1102 | id="text2297" | |
| 1103 | y="43.630859" | |
| 1104 | x="-363.15442" | |
| 1105 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1106 | xml:space="preserve"><tspan | |
| 1107 | y="43.630859" | |
| 1108 | x="-363.15442" | |
| 1109 | id="tspan2295" | |
| 1110 | sodipodi:role="line">Proposed</tspan></text> | |
| 1111 | <text | |
| 1112 | id="text2301" | |
| 1113 | y="314.01108" | |
| 1114 | x="98.034729" | |
| 1115 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1116 | xml:space="preserve"><tspan | |
| 1117 | id="tspan2299" | |
| 1118 | sodipodi:role="line" | |
| 1119 | x="98.034729" | |
| 1120 | y="314.01108">R Markdown</tspan></text> | |
| 1121 | <text | |
| 1122 | id="text2305" | |
| 1123 | y="285.84311" | |
| 1124 | x="107.43903" | |
| 1125 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1126 | xml:space="preserve"><tspan | |
| 1127 | id="tspan2303" | |
| 1128 | sodipodi:role="line" | |
| 1129 | x="107.43903" | |
| 1130 | y="285.84311">Markdown</tspan></text> | |
| 1131 | <text | |
| 1132 | id="text2309" | |
| 1133 | y="342.91147" | |
| 1134 | x="134.3277" | |
| 1135 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1136 | xml:space="preserve"><tspan | |
| 1137 | id="tspan2307" | |
| 1138 | sodipodi:role="line" | |
| 1139 | x="134.3277" | |
| 1140 | y="342.91147">XML</tspan></text> | |
| 1141 | <text | |
| 1142 | id="text2313" | |
| 1143 | y="370.34702" | |
| 1144 | x="113.56207" | |
| 1145 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1146 | xml:space="preserve"><tspan | |
| 1147 | id="tspan2311" | |
| 1148 | sodipodi:role="line" | |
| 1149 | x="113.56207" | |
| 1150 | y="370.34702">DocBook</tspan></text> | |
| 1151 | <text | |
| 1152 | id="text2317" | |
| 1153 | y="398.51498" | |
| 1154 | x="114.3526" | |
| 1155 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1156 | xml:space="preserve"><tspan | |
| 1157 | id="tspan2315" | |
| 1158 | sodipodi:role="line" | |
| 1159 | x="114.3526" | |
| 1160 | y="398.51498">AsciiDoc</tspan></text> | |
| 1161 | <text | |
| 1162 | transform="rotate(-90)" | |
| 1163 | id="text2329" | |
| 1164 | y="43.507812" | |
| 1165 | x="-774.87335" | |
| 1166 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1167 | xml:space="preserve"><tspan | |
| 1168 | y="43.507812" | |
| 1169 | x="-774.87335" | |
| 1170 | id="tspan2327" | |
| 1171 | sodipodi:role="line">Example Processing Combination</tspan></text> | |
| 1172 | <text | |
| 1173 | id="text2333" | |
| 1174 | y="562.05426" | |
| 1175 | x="135.31207" | |
| 1176 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#f3fbfe;fill-opacity:1;stroke:none" | |
| 1177 | xml:space="preserve"><tspan | |
| 1178 | y="562.05426" | |
| 1179 | x="135.31207" | |
| 1180 | id="tspan2331" | |
| 1181 | sodipodi:role="line">XML</tspan></text> | |
| 1182 | <text | |
| 1183 | id="text2337" | |
| 1184 | y="495.6918" | |
| 1185 | x="381.64142" | |
| 1186 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none" | |
| 1187 | xml:space="preserve"><tspan | |
| 1188 | y="495.6918" | |
| 1189 | x="381.64142" | |
| 1190 | id="tspan2335" | |
| 1191 | sodipodi:role="line">XSLT</tspan></text> | |
| 1192 | <text | |
| 1193 | id="text2341" | |
| 1194 | y="562.05426" | |
| 1195 | x="323.97742" | |
| 1196 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1197 | xml:space="preserve"><tspan | |
| 1198 | y="562.05426" | |
| 1199 | x="323.97742" | |
| 1200 | id="tspan2339" | |
| 1201 | sodipodi:role="line">XSLT Processor</tspan></text> | |
| 1202 | <text | |
| 1203 | id="text2345" | |
| 1204 | y="562.54059" | |
| 1205 | x="579.27557" | |
| 1206 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1207 | xml:space="preserve"><tspan | |
| 1208 | y="562.54059" | |
| 1209 | x="579.27557" | |
| 1210 | id="tspan2343" | |
| 1211 | sodipodi:role="line">R Markdown</tspan></text> | |
| 1212 | <text | |
| 1213 | id="text2349" | |
| 1214 | y="643.24084" | |
| 1215 | x="588.75018" | |
| 1216 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1217 | xml:space="preserve"><tspan | |
| 1218 | y="643.24084" | |
| 1219 | x="588.75018" | |
| 1220 | id="tspan2347" | |
| 1221 | sodipodi:role="line">Markdown</tspan></text> | |
| 1222 | <text | |
| 1223 | id="text2353" | |
| 1224 | y="642.63147" | |
| 1225 | x="339.61023" | |
| 1226 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1227 | xml:space="preserve"><tspan | |
| 1228 | y="642.63147" | |
| 1229 | x="339.61023" | |
| 1230 | id="tspan2351" | |
| 1231 | sodipodi:role="line">R Processor</tspan></text> | |
| 1232 | <text | |
| 1233 | id="text2357" | |
| 1234 | y="722.93903" | |
| 1235 | x="318.43912" | |
| 1236 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:21.3333px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1237 | xml:space="preserve"><tspan | |
| 1238 | y="722.93903" | |
| 1239 | x="318.43912" | |
| 1240 | id="tspan2355" | |
| 1241 | sodipodi:role="line">Variable Processor</tspan></text> | |
| 1242 | <text | |
| 1243 | id="text2361" | |
| 1244 | y="723.3316" | |
| 1245 | x="604.07831" | |
| 1246 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1247 | xml:space="preserve"><tspan | |
| 1248 | y="723.3316" | |
| 1249 | x="604.07831" | |
| 1250 | id="tspan2359" | |
| 1251 | sodipodi:role="line">HTML5</tspan></text> | |
| 1252 | <text | |
| 1253 | id="text2365" | |
| 1254 | y="630.84766" | |
| 1255 | x="81.211723" | |
| 1256 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none" | |
| 1257 | xml:space="preserve"><tspan | |
| 1258 | y="630.84766" | |
| 1259 | x="81.211723" | |
| 1260 | id="tspan2363" | |
| 1261 | sodipodi:role="line">Structured Data Source</tspan></text> | |
| 1262 | <text | |
| 1263 | id="text2369" | |
| 1264 | y="756.39404" | |
| 1265 | x="215.65826" | |
| 1266 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1267 | xml:space="preserve"><tspan | |
| 1268 | y="756.39404" | |
| 1269 | x="215.65826" | |
| 1270 | id="tspan2367" | |
| 1271 | sodipodi:role="line">interpolated values</tspan></text> | |
| 1272 | <g | |
| 1273 | transform="translate(-0.25585322,11.831789)" | |
| 1274 | id="g2523"> | |
| 1275 | <text | |
| 1276 | xml:space="preserve" | |
| 1277 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1278 | x="156.49219" | |
| 1279 | y="708.2467" | |
| 1280 | id="text2373"><tspan | |
| 1281 | sodipodi:role="line" | |
| 1282 | id="tspan2371" | |
| 1283 | x="156.49219" | |
| 1284 | y="708.2467">CSON</tspan></text> | |
| 1285 | <text | |
| 1286 | xml:space="preserve" | |
| 1287 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1288 | x="156.49219" | |
| 1289 | y="688.41504" | |
| 1290 | id="text2377"><tspan | |
| 1291 | sodipodi:role="line" | |
| 1292 | id="tspan2375" | |
| 1293 | x="156.49219" | |
| 1294 | y="688.41504">JSONNET</tspan></text> | |
| 1295 | <text | |
| 1296 | xml:space="preserve" | |
| 1297 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1298 | x="156.49219" | |
| 1299 | y="668.24695" | |
| 1300 | id="text2381"><tspan | |
| 1301 | sodipodi:role="line" | |
| 1302 | id="tspan2379" | |
| 1303 | x="156.49219" | |
| 1304 | y="668.24695">JSON5</tspan></text> | |
| 1305 | <text | |
| 1306 | xml:space="preserve" | |
| 1307 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1308 | x="156.49219" | |
| 1309 | y="648.07886" | |
| 1310 | id="text2385"><tspan | |
| 1311 | sodipodi:role="line" | |
| 1312 | id="tspan2383" | |
| 1313 | x="156.49219" | |
| 1314 | y="648.07886">JSON</tspan></text> | |
| 1315 | <text | |
| 1316 | xml:space="preserve" | |
| 1317 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1318 | x="94.110725" | |
| 1319 | y="648.41534" | |
| 1320 | id="text2389"><tspan | |
| 1321 | sodipodi:role="line" | |
| 1322 | id="tspan2387" | |
| 1323 | x="94.110725" | |
| 1324 | y="648.41534">YAML</tspan></text> | |
| 1325 | <text | |
| 1326 | xml:space="preserve" | |
| 1327 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1328 | x="94.110725" | |
| 1329 | y="668.24695" | |
| 1330 | id="text2393"><tspan | |
| 1331 | sodipodi:role="line" | |
| 1332 | id="tspan2391" | |
| 1333 | x="94.110725" | |
| 1334 | y="668.24695">TOML</tspan></text> | |
| 1335 | <text | |
| 1336 | xml:space="preserve" | |
| 1337 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1338 | x="94.110725" | |
| 1339 | y="688.41504" | |
| 1340 | id="text2397"><tspan | |
| 1341 | sodipodi:role="line" | |
| 1342 | id="tspan2395" | |
| 1343 | x="94.110725" | |
| 1344 | y="688.41504">XML</tspan></text> | |
| 1345 | </g> | |
| 1346 | <g | |
| 1347 | transform="translate(-1.2304677,-0.85937628)" | |
| 1348 | id="g2593"> | |
| 1349 | <g | |
| 1350 | id="g2532"> | |
| 1351 | <rect | |
| 1352 | id="rect4698" | |
| 1353 | ry="2.7292624" | |
| 1354 | y="91.740654" | |
| 1355 | x="129.16347" | |
| 1356 | height="32.205296" | |
| 1357 | width="54.039394" | |
| 1358 | style="fill:none;stroke:#05556e;stroke-width:2.72926;stroke-opacity:1" /> | |
| 1359 | <path | |
| 1360 | style="fill:#05556e;fill-opacity:1;stroke-width:0.272926" | |
| 1361 | id="path4700" | |
| 1362 | d="M 135.98663,117.12279 V 98.56381 h 5.45852 l 5.45853,6.82315 5.45852,-6.82315 h 5.45853 v 18.55898 h -5.45853 v -10.64412 l -5.45852,6.82315 -5.45853,-6.82315 v 10.64412 z m 34.11578,0 -8.18779,-9.00657 h 5.45852 v -9.55241 h 5.45853 v 9.55241 h 5.45852 z" | |
| 1363 | inkscape:connector-curvature="0" /> | |
| 1364 | </g> | |
| 1365 | <text | |
| 1366 | xml:space="preserve" | |
| 1367 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1368 | x="108.73981" | |
| 1369 | y="152.80437" | |
| 1370 | id="text2407"><tspan | |
| 1371 | sodipodi:role="line" | |
| 1372 | id="tspan2405" | |
| 1373 | x="108.73981" | |
| 1374 | y="152.80437">Markdown</tspan></text> | |
| 1375 | </g> | |
| 1376 | <path | |
| 1377 | inkscape:connector-curvature="0" | |
| 1378 | d="m 417.86562,272.90923 c -2.81873,0.35302 -5.58858,1.78683 -7.90222,4.10047 -1.79226,1.78682 -3.43787,4.20365 -5.01832,7.35911 -1.28173,2.56347 -2.29191,5.21927 -2.90019,7.59265 l -0.1738,0.68975 -0.68975,0.35302 c -0.96673,0.49423 -1.81398,1.01561 -2.77528,1.69993 -3.29666,2.35709 -6.15341,5.19211 -8.53222,8.46705 -0.23354,0.32586 -0.45621,0.58656 -0.49966,0.58656 -0.038,0 -0.33673,-0.0435 -0.65716,-0.0923 -0.73863,-0.11949 -3.19891,-0.13578 -4.11676,-0.0272 -3.79633,0.46164 -7.25593,1.57502 -11.41613,3.68228 -3.00339,1.5207 -4.93685,2.87304 -6.8323,4.77391 -2.37881,2.37882 -3.80176,5.01832 -4.21452,7.82076 -0.0978,0.62457 -0.0978,2.39511 0,3.0414 0.51052,3.55193 2.55804,6.94636 5.27358,8.74404 3.15003,2.08554 7.40256,2.6558 12.27424,1.65105 3.62253,-0.75492 7.20161,-2.14527 10.77526,-4.19822 3.47046,-1.99321 5.87643,-4.18193 7.57093,-6.87575 0.27155,-0.43449 0.35845,-0.52682 0.53224,-0.59199 2.79701,-1.01018 4.74677,-2.05295 6.96265,-3.72572 2.02036,-1.5207 3.43244,-2.85675 6.0991,-5.77324 0.68432,-0.74949 0.8038,-0.91785 0.84182,-1.16225 0.0326,-0.17379 0.0543,-0.20095 0.15207,-0.17922 0.51595,0.10319 2.20502,0.11948 2.94908,0.0272 2.08553,-0.25526 4.05701,-1.10251 6.01763,-2.57976 2.61778,-1.97691 5.06177,-5.27901 6.78885,-9.17853 2.59606,-5.86556 3.57908,-10.80785 3.01425,-15.19073 -0.14121,-1.12423 -0.28241,-1.74881 -0.59742,-2.71554 -0.42905,-1.29803 -1.08621,-2.55804 -1.89001,-3.62796 -0.43449,-0.57026 -1.57502,-1.70536 -2.14528,-2.12898 -1.59131,-1.17855 -3.93753,-2.13442 -6.03936,-2.46028 -0.66259,-0.10319 -2.29735,-0.14664 -2.85132,-0.0815 z m 2.44399,7.82076 c 1.94433,0.46707 3.2152,2.04751 3.5302,4.39917 0.0815,0.58656 0.0815,2.10183 0,2.7427 -0.32043,2.62864 -1.26544,5.70263 -2.61235,8.48878 -1.01561,2.10725 -1.79226,3.34011 -2.88933,4.58383 -0.32587,0.36931 -1.38493,1.31975 -1.42838,1.2763 -0.005,-0.005 0.0706,-0.34216 0.1738,-0.74406 0.24983,-0.97759 0.34215,-1.56958 0.3856,-2.41683 0.0706,-1.58044 -0.27155,-3.09571 -0.98302,-4.30684 -1.20027,-2.05295 -3.17175,-3.41072 -5.47453,-3.78547 -0.11405,-0.0163 -0.20638,-0.0489 -0.20638,-0.076 0,-0.0217 0.19552,-0.53768 0.42905,-1.15139 1.41752,-3.67684 2.66666,-5.83298 4.30142,-7.40799 1.0482,-1.01562 1.70536,-1.40665 2.73726,-1.62933 0.51596,-0.11405 1.49355,-0.0978 2.03666,0.0272 z m -10.34078,17.93885 c 0.52139,0.54311 0.56483,0.76579 0.46164,2.25933 l -0.0326,0.51596 -0.14121,-0.21725 c -0.22811,-0.34215 -0.40733,-0.72233 -0.52682,-1.1188 -0.0652,-0.20095 -0.15207,-0.43992 -0.20095,-0.53224 -0.0706,-0.13035 -0.17922,-0.91243 -0.19008,-1.34691 0,-0.11949 0.29871,0.0923 0.63,0.43991 z m -7.36997,3.01425 c 0.3856,2.28649 1.18397,4.05159 2.44941,5.40393 l 0.45078,0.47793 -0.13577,0.14664 c -0.0706,0.0815 -0.46165,0.51052 -0.86355,0.9613 -1.55328,1.73795 -2.81873,2.98167 -4.05158,3.97012 -0.41819,0.34216 -0.78208,0.61915 -0.79837,0.61915 -0.0163,0 -0.0435,-0.0923 -0.0652,-0.20638 -0.076,-0.4019 -0.46708,-1.4664 -0.8038,-2.15614 -0.54311,-1.12424 -1.14596,-2.0095 -2.08554,-3.0577 l -0.45621,-0.50509 0.41276,-0.50509 c 1.19484,-1.47182 2.92192,-3.26951 4.43177,-4.62728 0.85811,-0.76578 1.37949,-1.21656 1.39578,-1.20027 0.005,0.005 0.0597,0.315 0.11949,0.67888 z m -16.52135,9.77052 c -0.0163,0.11405 -0.0815,0.54311 -0.14664,0.9613 -0.22267,1.47182 -0.23353,3.57365 -0.0272,4.78478 0.19008,1.10251 0.57569,2.11812 1.08078,2.81873 0.27699,0.38018 0.87441,0.97759 1.22199,1.20027 l 0.23354,0.1575 -0.15207,0.12492 c -0.60285,0.48879 -2.54174,1.58044 -4.18193,2.34622 -2.4114,1.12967 -4.36659,1.7651 -6.62049,2.16157 -0.77664,0.13578 -0.99932,0.15207 -2.09096,0.15207 -0.98846,0 -1.30889,-0.0217 -1.67278,-0.0978 -1.5207,-0.33672 -2.53088,-0.97216 -3.1989,-2.0095 -0.53225,-0.82552 -0.72234,-1.48268 -0.72777,-2.43855 0,-1.56415 0.57027,-2.68296 2.17244,-4.27969 1.78682,-1.77597 3.93753,-3.05227 7.72299,-4.5784 2.01493,-0.81467 4.20366,-1.37407 5.75151,-1.4664 0.74406,-0.0434 0.66803,-0.0652 0.63544,0.16294 z m 6.13712,3.5302 c -0.0163,0.0543 -0.0272,0.0109 -0.0272,-0.0923 0,-0.10319 0.0109,-0.14664 0.0272,-0.0978 0.0109,0.0543 0.0109,0.14121 0,0.19009 z" | |
| 1379 | id="path8164" | |
| 1380 | style="fill:#df4d65;fill-opacity:1;stroke:none;stroke-width:0.00543108" /> | |
| 1381 | <g | |
| 1382 | transform="translate(1.378418e-5,1.0193503)" | |
| 1383 | id="g1168"> | |
| 1384 | <text | |
| 1385 | id="text1158" | |
| 1386 | y="364.17905" | |
| 1387 | x="349.05551" | |
| 1388 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1389 | xml:space="preserve"><tspan | |
| 1390 | id="tspan1156" | |
| 1391 | sodipodi:role="line" | |
| 1392 | x="349.05551" | |
| 1393 | y="364.17905">Processor</tspan></text> | |
| 1394 | <text | |
| 1395 | xml:space="preserve" | |
| 1396 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1397 | x="370.40707" | |
| 1398 | y="392.17905" | |
| 1399 | id="text1162"><tspan | |
| 1400 | y="392.17905" | |
| 1401 | x="370.40707" | |
| 1402 | sodipodi:role="line" | |
| 1403 | id="tspan1160">Chain</tspan></text> | |
| 1404 | </g> | |
| 1405 | <g | |
| 1406 | transform="translate(0,-2.3144459)" | |
| 1407 | id="g1206"> | |
| 1408 | <text | |
| 1409 | xml:space="preserve" | |
| 1410 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1411 | x="586.44855" | |
| 1412 | y="327.56967" | |
| 1413 | id="text1190"><tspan | |
| 1414 | y="327.56967" | |
| 1415 | x="586.44855" | |
| 1416 | sodipodi:role="line" | |
| 1417 | id="tspan1188">Processor-</tspan></text> | |
| 1418 | <text | |
| 1419 | id="text1194" | |
| 1420 | y="355.56967" | |
| 1421 | x="588.43488" | |
| 1422 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1423 | xml:space="preserve"><tspan | |
| 1424 | id="tspan1192" | |
| 1425 | sodipodi:role="line" | |
| 1426 | x="588.43488" | |
| 1427 | y="355.56967">dependent</tspan></text> | |
| 1428 | </g> | |
| 1429 | </svg> | |
| 1 | 1430 |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> | |
| 2 | <title>HTML5 Logo</title> | |
| 3 | <path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/> | |
| 4 | <path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/> | |
| 5 | <path fill="#f16529" d="M256 480.5V131H404.3L376 447"/> | |
| 6 | <path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/> | |
| 7 | <path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/> | |
| 8 | </svg> |
| 1 | <?xml version="1.0" standalone="no"?> | |
| 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | |
| 3 | "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | |
| 4 | <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | |
| 5 | width="1280.000000pt" height="1123.000000pt" viewBox="0 0 1280.000000 1123.000000" | |
| 6 | preserveAspectRatio="xMidYMid meet"> | |
| 7 | <metadata> | |
| 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 | |
| 9 | </metadata> | |
| 10 | <g transform="translate(0.000000,1123.000000) scale(0.100000,-0.100000)" | |
| 11 | fill="#000000" stroke="none"> | |
| 12 | <path d="M10280 11220 c-519 -65 -1029 -329 -1455 -755 -330 -329 -633 -774 | |
| 13 | -924 -1355 -236 -472 -422 -961 -534 -1398 l-32 -127 -127 -65 c-178 -91 -334 | |
| 14 | -187 -511 -313 -607 -434 -1133 -956 -1571 -1559 -43 -60 -84 -108 -92 -108 | |
| 15 | -7 0 -62 8 -121 17 -136 22 -589 25 -758 5 -699 -85 -1336 -290 -2102 -678 | |
| 16 | -553 -280 -909 -529 -1258 -879 -438 -438 -700 -924 -776 -1440 -18 -115 -18 | |
| 17 | -441 0 -560 94 -654 471 -1279 971 -1610 580 -384 1363 -489 2260 -304 667 | |
| 18 | 139 1326 395 1984 773 639 367 1082 770 1394 1266 50 80 66 97 98 109 515 186 | |
| 19 | 874 378 1282 686 372 280 632 526 1123 1063 126 138 148 169 155 214 6 32 10 | |
| 20 | 37 28 33 95 -19 406 -22 543 -5 384 47 747 203 1108 475 482 364 932 972 1250 | |
| 21 | 1690 478 1080 659 1990 555 2797 -26 207 -52 322 -110 500 -79 239 -200 471 | |
| 22 | -348 668 -80 105 -290 314 -395 392 -293 217 -725 393 -1112 453 -122 19 -423 | |
| 23 | 27 -525 15z m450 -1440 c358 -86 592 -377 650 -810 15 -108 15 -387 0 -505 | |
| 24 | -59 -484 -233 -1050 -481 -1563 -187 -388 -330 -615 -532 -844 -60 -68 -255 | |
| 25 | -243 -263 -235 -1 1 13 63 32 137 46 180 63 289 71 445 13 291 -50 570 -181 | |
| 26 | 793 -221 378 -584 628 -1008 697 -21 3 -38 9 -38 14 0 4 36 99 79 212 261 677 | |
| 27 | 491 1074 792 1364 193 187 314 259 504 300 95 21 275 18 375 -5z m-1904 -3303 | |
| 28 | c96 -100 104 -141 85 -416 l-6 -95 -26 40 c-42 63 -75 133 -97 206 -12 37 -28 | |
| 29 | 81 -37 98 -13 24 -33 168 -35 248 0 22 55 -17 116 -81z m-1357 -555 c71 -421 | |
| 30 | 218 -746 451 -995 l83 -88 -25 -27 c-13 -15 -85 -94 -159 -177 -286 -320 -519 | |
| 31 | -549 -746 -731 -77 -63 -144 -114 -147 -114 -3 0 -8 17 -12 38 -14 74 -86 270 | |
| 32 | -148 397 -100 207 -211 370 -384 563 l-84 93 76 93 c220 271 538 602 816 852 | |
| 33 | 158 141 254 224 257 221 1 -1 11 -58 22 -125z m-3042 -1799 c-3 -21 -15 -100 | |
| 34 | -27 -177 -41 -271 -43 -658 -5 -881 35 -203 106 -390 199 -519 51 -70 161 | |
| 35 | -180 225 -221 l43 -29 -28 -23 c-111 -90 -468 -291 -770 -432 -444 -208 -804 | |
| 36 | -325 -1219 -398 -143 -25 -184 -28 -385 -28 -182 0 -241 4 -308 18 -280 62 | |
| 37 | -466 179 -589 370 -98 152 -133 273 -134 449 0 288 105 494 400 788 329 327 | |
| 38 | 725 562 1422 843 371 150 774 253 1059 270 137 8 123 12 117 -30z m1130 -650 | |
| 39 | c-3 -10 -5 -2 -5 17 0 19 2 27 5 18 2 -10 2 -26 0 -35z"/> | |
| 40 | </g> | |
| 41 | </svg> | |
| 1 | 42 |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="208" height="128" viewBox="0 0 208 128"><rect width="198" height="118" x="5" y="5" ry="10" stroke="#000" stroke-width="10" fill="none"/><path d="M30 98V30h20l20 25 20-25h20v68H90V59L70 84 50 59v39zm125 0l-30-33h20V30h20v35h20z"/></svg> |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to use the [R](https://www.r-project.org/) | |
| 4 | programming language from within the application. The application uses an | |
| 5 | interpreter known as [Renjin](https://www.renjin.org/) to integrate with R. | |
| 6 | ||
| 7 | # Hello world | |
| 8 | ||
| 9 | Complete the following steps to see R in action: | |
| 10 | ||
| 11 | 1. Start the application. | |
| 12 | 1. Click **File → New** to create a new file. | |
| 13 | 1. Click **File → Save As**. | |
| 14 | 1. Set **Name** to: `addition.Rmd` | |
| 15 | 1. Click **Save**. | |
| 16 | ||
| 17 | Setting the file name extension tells the application what processor to | |
| 18 | use when transforming the contents for display in the preview pane. Continue | |
| 19 | by typing in the following text, including the backticks: | |
| 20 | ||
| 21 | ```r | |
| 22 | `r#1 + 1` | |
| 23 | ``` | |
| 24 | ||
| 25 | The preview pane shows the result of `1` plus `1`: | |
| 26 | ||
| 27 | ``` | |
| 28 | 2.0 | |
| 29 | ``` | |
| 30 | ||
| 31 | # Bootstrap script | |
| 32 | ||
| 33 | Being able to run R code while editing an R Markdown document is convenient. | |
| 34 | Having the ability to call functions is where the power of R can be | |
| 35 | leveraged. | |
| 36 | ||
| 37 | Complete the following steps to call an R function from your own library: | |
| 38 | ||
| 39 | 1. Click **File → New** to create a new file. | |
| 40 | 1. Click **File → Save As**. | |
| 41 | 1. Browse to your home directory. | |
| 42 | 1. Set **Name** to: `library.R`. | |
| 43 | 1. Click **Save**. | |
| 44 | 1. Set the contents to: | |
| 45 | ``` r | |
| 46 | sum <- function( a, b ) { | |
| 47 | a + b | |
| 48 | } | |
| 49 | ``` | |
| 50 | 1. Click the **Save** icon. | |
| 51 | 1. Click **R → Script**. | |
| 52 | 1. Set the **R Startup Script** contents to: | |
| 53 | ``` r | |
| 54 | source( 'library.R' ); | |
| 55 | ``` | |
| 56 | 1. Click **OK**. | |
| 57 | 1. Create a new file. | |
| 58 | 1. Set the contents to: | |
| 59 | ``` r | |
| 60 | `r#sum( 5, 5 )` | |
| 61 | ``` | |
| 62 | 1. Save the file as `sum.R`. | |
| 63 | ||
| 64 | The preview panel shows the result of calling the `sum` function: | |
| 65 | ||
| 66 | ``` | |
| 67 | 10.0 | |
| 68 | ``` | |
| 69 | ||
| 70 | This shows how the bootstrap script can load `library.R`, which defines | |
| 71 | a `sum` function that is called by name in the Markdown document. | |
| 72 | ||
| 73 | # Working directory | |
| 74 | ||
| 75 | R files may be sourced from any directory, not just the user's home | |
| 76 | directory. Accomplish this as follows: | |
| 77 | ||
| 78 | 1. Click **R → Directory**. | |
| 79 | 1. Set **Directory** to a different directory. | |
| 80 | 1. Click **OK**. | |
| 81 | 1. Create the directory if it does not exist. | |
| 82 | 1. Move `library.R` into the directory. | |
| 83 | 1. Append a new function to `library.R` as follows: | |
| 84 | ``` r | |
| 85 | mul <- function( a, b ) { | |
| 86 | a * b | |
| 87 | } | |
| 88 | ``` | |
| 89 | 1. Click **R → Script**. | |
| 90 | 1. Set the **R Startup Script** contents to: | |
| 91 | ``` r | |
| 92 | setwd( '$application.r.working.directory$' ); | |
| 93 | source( 'library.R' ); | |
| 94 | ``` | |
| 95 | 1. Change `sum.Rmd` to: | |
| 96 | ``` r | |
| 97 | `r#mul( 5, 5 )` | |
| 98 | ``` | |
| 99 | 1. Close the file `sum.Rmd`. | |
| 100 | 1. Confirm saving the file when prompted. | |
| 101 | 1. Re-open `sum.Rmd`. | |
| 102 | ||
| 103 | The preview panel shows: | |
| 104 | ||
| 105 | ``` | |
| 106 | 25.0 | |
| 107 | ``` | |
| 108 | ||
| 109 | Calling `setwd` using `'$application.r.working.directory$'` changes the | |
| 110 | working directory where the R engine searches for source files. | |
| 111 | ||
| 112 | # YAML definitions | |
| 113 | ||
| 114 | To see how variable definitions work in R, try the following: | |
| 115 | ||
| 116 | 1. Create a new file. | |
| 117 | 1. Change the contents to (use spaces not tabs): | |
| 118 | ``` yaml | |
| 119 | project: | |
| 120 | title: Project Title | |
| 121 | author: Author Name | |
| 122 | ``` | |
| 123 | 1. Save the file as `definitions.yaml`. | |
| 124 | 1. Click **File → Open**. | |
| 125 | 1. Set **Source Files** to **Definition Files**. | |
| 126 | 1. Select `definitions.yaml`. | |
| 127 | 1. Click **Open**. | |
| 128 | 1. Open `sum.Rmd` if it is not already open. | |
| 129 | 1. Type: `je` | |
| 130 | 1. Press `Ctrl+Space` | |
| 131 | ||
| 132 | The editor inserts the following text (matches `je` against Pro**je**ct): | |
| 133 | ||
| 134 | ``` r | |
| 135 | `r#x( v$project$title )` | |
| 136 | ``` | |
| 137 | ||
| 138 | The preview panel shows: | |
| 139 | ||
| 140 | ``` | |
| 141 | r#x( 'Project Title' ) | |
| 142 | ``` | |
| 143 | ||
| 144 | This is because the application inserts definition reference names based | |
| 145 | on the type of file being edited. By default, the R engine does not have | |
| 146 | a function named `x` defined. | |
| 147 | ||
| 148 | Continue as follows: | |
| 149 | ||
| 150 | 1. Click **R → Script**. | |
| 151 | 1. Append the following: | |
| 152 | ``` r | |
| 153 | x <- function( s ) { | |
| 154 | tryCatch( { | |
| 155 | r = eval( parse( text = s ) ) | |
| 156 | ||
| 157 | ifelse( is.atomic( r ), r, s ); | |
| 158 | }, | |
| 159 | warning = function( w ) { s }, | |
| 160 | error = function( e ) { s } ) | |
| 161 | } | |
| 162 | ``` | |
| 163 | 1. Click **OK**. | |
| 164 | 1. Close and re-open `sum.Rmd`. | |
| 165 | ||
| 166 | The preview panel shows: | |
| 167 | ||
| 168 | ``` | |
| 169 | 25.0 | |
| 170 | ||
| 171 | Project Title | |
| 172 | ``` | |
| 173 | ||
| 174 | The `x` function attempts to evaluate the expression defined by the YAML | |
| 175 | variable. This means that the YAML definitions can also include expressions | |
| 176 | that R is capable of evaluating. | |
| 177 | ||
| 178 | While the `x` function can be defined within the R Startup Script, it is | |
| 179 | better practice to put it into its own library so that it can be reused | |
| 180 | outside of the application. | |
| 181 | ||
| 1 | 182 |
| 1 | # Introduction | |
| 2 | ||
| 3 | The Scalable Vector Graphics (SVG) drawing software---[Batik](https://xmlgraphics.apache.org/batik/)---that's used by the application may be unable to read certain SVG files produced by [Inkscape](https://inkscape.org/). The result is that embedding the vector graphics files may trigger the following issues: | |
| 4 | ||
| 5 | * Unable to create nested element | |
| 6 | * Black blocks, no text displayed | |
| 7 | * Black text instead of coloured | |
| 8 | ||
| 9 | The remainder of this document explains these problems and how to fix them. | |
| 10 | ||
| 11 | # Nested element | |
| 12 | ||
| 13 | When referencing a vector graphic using Markdown, the status bar may show the following error: | |
| 14 | ||
| 15 | > The current document is unable to create an element of the requested type (namespace: http://www.w3.org/2000/svg, name: flowRoot). | |
| 16 | ||
| 17 | This error is due to a version mismatch of the `flowRoot` element that Inkscape creates. | |
| 18 | ||
| 19 | ## Fix | |
| 20 | ||
| 21 | Resolve the issue by changing the SVG version number as follows: | |
| 22 | ||
| 23 | 1. Edit the vector graphics file using any text editor. | |
| 24 | 1. Find `version="1.1"` and change it to `version="1.2"`. | |
| 25 | 1. Save the file. | |
| 26 | ||
| 27 | The SVG will now appear inside the application; however, the text may appear as black blocks. | |
| 28 | ||
| 29 | # Black blocks | |
| 30 | ||
| 31 | Depending on how text is added to a vector graphic in Inkscape, the text may be inserted within an element called a `flowRoot`. Although Batik recognizes `flowRoot` for SVG version 1.2, it cannot fully interpret the contents. Black blocks are drawn instead of the text, such as those depicted in the following figure: | |
| 32 | ||
| 33 |  | |
| 34 | ||
| 35 | ## Fix | |
| 36 | ||
| 37 | Resolve the issue by "unflowing" all text elements as follows: | |
| 38 | ||
| 39 | 1. Start Inkscape. | |
| 40 | 1. Load the SVG file. | |
| 41 | 1. Select all the text elements. | |
| 42 | 1. Click **Text → Unflow**. | |
| 43 | ||
| 44 | The text may change size and position; recreate the text without dragging using the text tool. After all the text areas have been recreated, continue as follows: | |
| 45 | ||
| 46 | 1. Click **Edit → XML Editor**. | |
| 47 | 1. Expand the **XML Editor** to see more elements. | |
| 48 | 1. Delete all elements named `svg:flowRoot`. | |
| 49 | 1. Save the file. | |
| 50 | ||
| 51 | When the illustration is reloaded, the black blocks will have disappeared, but the text elements ignore any assigned colour. | |
| 52 | ||
| 53 | # Black text | |
| 54 | ||
| 55 | When an SVG `style` attribute contains a reference to `-inkscape-font-specification`, Batik ignores all values that follow said reference. This results in black text, such as: | |
| 56 | ||
| 57 |  | |
| 58 | ||
| 59 | ## Fix | |
| 60 | ||
| 61 | Resolve the issue of colourless text as follows: | |
| 62 | ||
| 63 | 1. Open the SVG file in a plain text editor. | |
| 64 | 1. Remove all references `-inkscape-font-specification:'<FONT>';`, including the trailing (or leading) semicolon. | |
| 65 | 1. Save the file. | |
| 66 | ||
| 67 | When the illustration is reloaded, the colours will have reappeared, such as: | |
| 68 | ||
| 69 |  | |
| 70 | ||
| 1 | 71 |
| 1 | # Real-time equation rendering | |
| 2 | ||
| 3 | With interpolated variables and R calculations: | |
| 4 | ||
| 5 | $\sqrt{`r#x( v$formula$sqrt$value)`} = `r# round(sqrt(x( v$formula$sqrt$value )),5)`$ | |
| 6 | ||
| 7 | # Maxwell's equations | |
| 8 | ||
| 9 | $rot \vec{E} = \frac{1}{c} \frac{\partial{\vec{B}}}{\partial t}, div \vec{B} = 0$ | |
| 10 | ||
| 11 | $rot \vec{B} = \frac{1}{c} \frac{\partial{\vec{E}}}{\partial t} + \frac{4\pi}{c} \vec{j}, div \vec{E} = 4 \pi \rho_{\varepsilon}$ | |
| 12 | ||
| 13 | # Time-dependent Schrödinger equation | |
| 14 | ||
| 15 | $- \frac{{\hbar ^2 }}{{2m}}\frac{{\partial ^2 \psi (x,t)}}{{\partial x^2 }} + U(x)\psi (x,t) = i\hbar \frac{{\partial \psi (x,t)}}{{\partial t}}$ | |
| 16 | ||
| 17 | # Discrete-time Fourier transforms | |
| 18 | ||
| 19 | Unit step function: $u(n) \Leftrightarrow \frac{1}{1-e^{-jw}} + \sum_{k=-\infty}^{\infty} \pi \delta (\omega + 2\pi k)$ | |
| 20 | ||
| 21 | Shifted delta: $\delta (n - n_o ) \Leftrightarrow e^{ - j\omega n_o }$ | |
| 22 | ||
| 23 | # Faraday's Law | |
| 24 | ||
| 25 | $\oint_C {E \cdot d\ell = - \frac{d}{{dt}}} \int_S {B_n dA}$ | |
| 26 | ||
| 27 | # Infinite series | |
| 28 | ||
| 29 | $sin(x) = \sum_{n = 1}^{\infty} {\frac{{( { - 1})^{n - 1} x^{2n - 1} }}{{( {2n - 1})!}}}$ | |
| 30 | ||
| 31 | # Magnetic flux | |
| 32 | ||
| 33 | $\phi _m = \int_S {N{{B}} \cdot {{\hat n}}dA = } \int_S {NB_n dA}$ | |
| 34 | ||
| 35 | # Driven oscillation amplitude | |
| 36 | ||
| 37 | $A = \frac{{F_0 }}{{\sqrt {m^2 ( {\omega _0^2 - \omega ^2 } )^2 + b^2 \omega ^2 } }}$ | |
| 38 | ||
| 39 | # Optics | |
| 40 | ||
| 41 | $\phi = \frac{{2\pi }}{\lambda }a sin(\theta)$ | |
| 1 | 42 |
| 1 | formula: | |
| 2 | sqrt: | |
| 3 | value: 603 | |
| 4 | ||
| 1 | 5 |
| 1 | org.gradle.jvmargs=-Xmx1G -XX:MaxPermSize=512m | |
| 2 | ||
| 1 | 3 |
| 1 | <svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www.w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c.332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531.03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2.511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-.367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1.699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094.0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5.0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-.195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4.191406-3.652344.207031-.015625.414063-.015625.617187-.007812l.933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2.820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3.429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3.429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 .140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281.808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2.472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4.285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1.9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-.667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1.253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 0'/></g></svg> | |
| 1 | 2 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | # --------------------------------------------------------------------------- | |
| 4 | # This script cross-compiles application launchers for different platforms. | |
| 5 | # | |
| 6 | # The application binaries are self-contained launchers that do not need | |
| 7 | # to be installed. | |
| 8 | # --------------------------------------------------------------------------- | |
| 9 | ||
| 10 | source $HOME/bin/build-template | |
| 11 | ||
| 12 | readonly APP_NAME=$(find "${SCRIPT_DIR}/src" -type f -name "settings.properties" -exec cat {} \; | grep "application.title=" | cut -d'=' -f2) | |
| 13 | readonly FILE_APP_JAR="${APP_NAME}.jar" | |
| 14 | ||
| 15 | ARG_JAVA_OS="linux" | |
| 16 | ARG_JAVA_ARCH="amd64" | |
| 17 | ARG_JAVA_VERSION="14.0.2" | |
| 18 | ARG_JAVA_UPDATE="13" | |
| 19 | ARG_JAVA_DIR="java" | |
| 20 | ||
| 21 | ARG_DIR_DIST="dist" | |
| 22 | ||
| 23 | FILE_DIST_EXEC="run.sh" | |
| 24 | ||
| 25 | ARG_PATH_DIST_JAR="${SCRIPT_DIR}/build/libs/${FILE_APP_JAR}" | |
| 26 | ||
| 27 | DEPENDENCIES=( | |
| 28 | "gradle,https://gradle.org" | |
| 29 | "warp-packer,https://github.com/dgiagio/warp" | |
| 30 | "tar,https://www.gnu.org/software/tar" | |
| 31 | "unzip,http://infozip.sourceforge.net" | |
| 32 | ) | |
| 33 | ||
| 34 | ARGUMENTS+=( | |
| 35 | "a,arch,Target operating system architecture (amd64)" | |
| 36 | "b,build,Suppress building application" | |
| 37 | "o,os,Target operating system (linux, windows, mac)" | |
| 38 | "u,update,Java update version number (${ARG_JAVA_UPDATE})" | |
| 39 | "v,version,Full Java version (${ARG_JAVA_VERSION})" | |
| 40 | ) | |
| 41 | ||
| 42 | ARCHIVE_EXT="tar.gz" | |
| 43 | ARCHIVE_APP="tar xf" | |
| 44 | APP_EXTENSION="bin" | |
| 45 | ||
| 46 | # --------------------------------------------------------------------------- | |
| 47 | # Generates | |
| 48 | # --------------------------------------------------------------------------- | |
| 49 | execute() { | |
| 50 | $do_configure_target | |
| 51 | $do_build | |
| 52 | $do_clean | |
| 53 | ||
| 54 | pushd "${ARG_DIR_DIST}" > /dev/null 2>&1 | |
| 55 | ||
| 56 | $do_extract_java | |
| 57 | $do_create_launch_script | |
| 58 | $do_copy_archive | |
| 59 | ||
| 60 | popd > /dev/null 2>&1 | |
| 61 | ||
| 62 | $do_create_launcher | |
| 63 | ||
| 64 | return 1 | |
| 65 | } | |
| 66 | ||
| 67 | # --------------------------------------------------------------------------- | |
| 68 | # Configure platform-specific commands and file names. | |
| 69 | # --------------------------------------------------------------------------- | |
| 70 | utile_configure_target() { | |
| 71 | if [ "${ARG_JAVA_OS}" = "windows" ]; then | |
| 72 | ARCHIVE_EXT="zip" | |
| 73 | ARCHIVE_APP="unzip -qq" | |
| 74 | FILE_DIST_EXEC="run.bat" | |
| 75 | APP_EXTENSION="exe" | |
| 76 | do_create_launch_script=utile_create_launch_script_windows | |
| 77 | fi | |
| 78 | } | |
| 79 | ||
| 80 | # --------------------------------------------------------------------------- | |
| 81 | # Build platform-specific überjar. | |
| 82 | # --------------------------------------------------------------------------- | |
| 83 | utile_build() { | |
| 84 | $log "Build application for ${ARG_JAVA_OS}" | |
| 85 | gradle clean jar -PtargetOs="${ARG_JAVA_OS}" | |
| 86 | } | |
| 87 | ||
| 88 | # --------------------------------------------------------------------------- | |
| 89 | # Purges the existing distribution directory to recreate the launcher. | |
| 90 | # This refreshes the JRE from the downloaded archive. | |
| 91 | # --------------------------------------------------------------------------- | |
| 92 | utile_clean() { | |
| 93 | $log "Recreate ${ARG_DIR_DIST}" | |
| 94 | rm -rf "${ARG_DIR_DIST}" | |
| 95 | mkdir -p "${ARG_DIR_DIST}" | |
| 96 | } | |
| 97 | ||
| 98 | # --------------------------------------------------------------------------- | |
| 99 | # Extract platform-specific Java Runtime Environment. This will download | |
| 100 | # and cache the required Java Runtime Environment for the target platform. | |
| 101 | # On subsequent runs, the cached version is used, instead of issuing another | |
| 102 | # download. | |
| 103 | # --------------------------------------------------------------------------- | |
| 104 | utile_extract_java() { | |
| 105 | $log "Extract Java" | |
| 106 | local -r java_vm="jre" | |
| 107 | local -r java_version="${ARG_JAVA_VERSION}+${ARG_JAVA_UPDATE}" | |
| 108 | local -r url_java="https://download.bell-sw.com/java/${java_version}/bellsoft-${java_vm}${java_version}-${ARG_JAVA_OS}-${ARG_JAVA_ARCH}-full.${ARCHIVE_EXT}" | |
| 109 | ||
| 110 | local -r file_java="${java_vm}-${java_version}-${ARG_JAVA_OS}-${ARG_JAVA_ARCH}.${ARCHIVE_EXT}" | |
| 111 | local -r path_java="/tmp/${file_java}" | |
| 112 | ||
| 113 | # File must have contents. | |
| 114 | if [ ! -s ${path_java} ]; then | |
| 115 | $log "Download ${url_java} to ${path_java}" | |
| 116 | wget -q "${url_java}" -O "${path_java}" | |
| 117 | fi | |
| 118 | ||
| 119 | $log "Unpack ${path_java}" | |
| 120 | $ARCHIVE_APP "${path_java}" | |
| 121 | ||
| 122 | local -r dir_java="${java_vm}-${ARG_JAVA_VERSION}-full" | |
| 123 | ||
| 124 | $log "Rename ${dir_java} to ${ARG_JAVA_DIR}" | |
| 125 | mv "${dir_java}" "${ARG_JAVA_DIR}" | |
| 126 | } | |
| 127 | ||
| 128 | # --------------------------------------------------------------------------- | |
| 129 | # Create Linux-specific launch script. | |
| 130 | # --------------------------------------------------------------------------- | |
| 131 | utile_create_launch_script_linux() { | |
| 132 | $log "Create Linux launch script" | |
| 133 | ||
| 134 | cat > "${FILE_DIST_EXEC}" << __EOT | |
| 135 | #!/usr/bin/env bash | |
| 136 | ||
| 137 | readonly SCRIPT_SRC="\$(dirname "\${BASH_SOURCE[\${#BASH_SOURCE[@]} - 1]}")" | |
| 138 | ||
| 139 | "\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@" 2>&1 >/dev/null & | |
| 140 | __EOT | |
| 141 | ||
| 142 | chmod +x "${FILE_DIST_EXEC}" | |
| 143 | } | |
| 144 | ||
| 145 | # --------------------------------------------------------------------------- | |
| 146 | # Create Windows-specific launch script. | |
| 147 | # --------------------------------------------------------------------------- | |
| 148 | utile_create_launch_script_windows() { | |
| 149 | $log "Create Windows launch script" | |
| 150 | ||
| 151 | cat > "${FILE_DIST_EXEC}" << __EOT | |
| 152 | @echo off | |
| 153 | ||
| 154 | set SCRIPT_DIR=%~dp0 | |
| 155 | "%SCRIPT_DIR%\\${ARG_JAVA_DIR}\\bin\\java" -jar "%SCRIPT_DIR%\\scrivenvar.jar" %* | |
| 156 | __EOT | |
| 157 | ||
| 158 | # Convert Unix end of line characters (\n) to Windows format (\r\n). | |
| 159 | # This avoids any potential line conversion issues with the repository. | |
| 160 | sed -i 's/$/\r/' "${FILE_DIST_EXEC}" | |
| 161 | } | |
| 162 | ||
| 163 | # --------------------------------------------------------------------------- | |
| 164 | # Copy application überjar. | |
| 165 | # --------------------------------------------------------------------------- | |
| 166 | utile_copy_archive() { | |
| 167 | $log "Create copy of ${FILE_APP_JAR}" | |
| 168 | cp "${ARG_PATH_DIST_JAR}" "${FILE_APP_JAR}" | |
| 169 | } | |
| 170 | ||
| 171 | # --------------------------------------------------------------------------- | |
| 172 | # Create platform-specific launcher binary. | |
| 173 | # --------------------------------------------------------------------------- | |
| 174 | utile_create_launcher() { | |
| 175 | $log "Create ${APP_NAME}.${APP_EXTENSION}" | |
| 176 | ||
| 177 | # Download uses amd64, but warp-packer differs. | |
| 178 | if [ "${ARG_JAVA_ARCH}" = "amd64" ]; then | |
| 179 | ARG_JAVA_ARCH="x64" | |
| 180 | fi | |
| 181 | ||
| 182 | warp-packer \ | |
| 183 | --arch "${ARG_JAVA_OS}-${ARG_JAVA_ARCH}" \ | |
| 184 | --input_dir "${ARG_DIR_DIST}" \ | |
| 185 | --exec "${FILE_DIST_EXEC}" \ | |
| 186 | --output "${APP_NAME}.${APP_EXTENSION}" > /dev/null | |
| 187 | ||
| 188 | chmod +x "${APP_NAME}.${APP_EXTENSION}" | |
| 189 | } | |
| 190 | ||
| 191 | argument() { | |
| 192 | local consume=2 | |
| 193 | ||
| 194 | case "$1" in | |
| 195 | -a|--arch) | |
| 196 | ARG_JAVA_ARCH="$2" | |
| 197 | ;; | |
| 198 | -b|--build) | |
| 199 | do_build=noop | |
| 200 | consume=1 | |
| 201 | ;; | |
| 202 | -o|--os) | |
| 203 | ARG_JAVA_OS="$2" | |
| 204 | ;; | |
| 205 | -u|--update) | |
| 206 | ARG_JAVA_UPDATE="$2" | |
| 207 | ;; | |
| 208 | -v|--version) | |
| 209 | ARG_JAVA_VERSION="$2" | |
| 210 | ;; | |
| 211 | esac | |
| 212 | ||
| 213 | return ${consume} | |
| 214 | } | |
| 215 | ||
| 216 | do_configure_target=utile_configure_target | |
| 217 | do_build=utile_build | |
| 218 | do_clean=utile_clean | |
| 219 | do_extract_java=utile_extract_java | |
| 220 | do_create_launch_script=utile_create_launch_script_linux | |
| 221 | do_copy_archive=utile_copy_archive | |
| 222 | do_create_launcher=utile_create_launcher | |
| 223 | ||
| 224 | main "$@" | |
| 225 | ||
| 1 | 226 |
| 1 | Released into the Public Domain by David Croft. | |
| 2 | ||
| 3 | http://www.davidc.net/programming/java/java-preferences-using-file-backing-store | |
| 4 | http://creativecommons.org/publicdomain/zero/1.0/ | |
| 5 | ||
| 6 | CC0 1.0 Universal (CC0 1.0) | |
| 7 | ||
| 8 | Public Domain Dedication | |
| 9 | ||
| 10 | This is a human-readable summary of the Legal Code (read the full text). | |
| 11 | ||
| 12 | Disclaimer | |
| 13 | ||
| 14 | No Copyright | |
| 15 | ||
| 16 | * The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. | |
| 17 | ||
| 18 | * You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See Other Information below. | |
| 19 | ||
| 20 | This license is acceptable for Free Cultural Works. | |
| 21 | ||
| 22 | Other Information | |
| 23 | ||
| 24 | * In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. | |
| 25 | * Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. | |
| 26 | * When using or citing the work, you should not imply endorsement by the author or the affirmer. | |
| 27 | ||
| 1 | 28 |
| 1 | Copyright (c) 2015-2016, Atlassian Pty Ltd | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Copyright (c) 2016-2018, Vladimir Schneider, | |
| 5 | All rights reserved. | |
| 6 | ||
| 7 | Redistribution and use in source and binary forms, with or without | |
| 8 | modification, are permitted provided that the following conditions are met: | |
| 9 | ||
| 10 | * Redistributions of source code must retain the above copyright notice, this | |
| 11 | list of conditions and the following disclaimer. | |
| 12 | ||
| 13 | * Redistributions in binary form must reproduce the above copyright notice, | |
| 14 | this list of conditions and the following disclaimer in the documentation | |
| 15 | and/or other materials provided with the distribution. | |
| 16 | ||
| 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 27 |
| 1 | Copyright (c) 2014, TomasMikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright notice, this | |
| 8 | list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | this list of conditions and the following disclaimer in the documentation | |
| 12 | and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 24 |
| 1 | 1 | |
| 2 | Apache License | |
| 3 | Version 2.0, January 2004 | |
| 4 | http://www.apache.org/licenses/ | |
| 5 | ||
| 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 7 | ||
| 8 | 1. Definitions. | |
| 9 | ||
| 10 | "License" shall mean the terms and conditions for use, reproduction, | |
| 11 | and distribution as defined by Sections 1 through 9 of this document. | |
| 12 | ||
| 13 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 14 | the copyright owner that is granting the License. | |
| 15 | ||
| 16 | "Legal Entity" shall mean the union of the acting entity and all | |
| 17 | other entities that control, are controlled by, or are under common | |
| 18 | control with that entity. For the purposes of this definition, | |
| 19 | "control" means (i) the power, direct or indirect, to cause the | |
| 20 | direction or management of such entity, whether by contract or | |
| 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 22 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 23 | ||
| 24 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 25 | exercising permissions granted by this License. | |
| 26 | ||
| 27 | "Source" form shall mean the preferred form for making modifications, | |
| 28 | including but not limited to software source code, documentation | |
| 29 | source, and configuration files. | |
| 30 | ||
| 31 | "Object" form shall mean any form resulting from mechanical | |
| 32 | transformation or translation of a Source form, including but | |
| 33 | not limited to compiled object code, generated documentation, | |
| 34 | and conversions to other media types. | |
| 35 | ||
| 36 | "Work" shall mean the work of authorship, whether in Source or | |
| 37 | Object form, made available under the License, as indicated by a | |
| 38 | copyright notice that is included in or attached to the work | |
| 39 | (an example is provided in the Appendix below). | |
| 40 | ||
| 41 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 42 | form, that is based on (or derived from) the Work and for which the | |
| 43 | editorial revisions, annotations, elaborations, or other modifications | |
| 44 | represent, as a whole, an original work of authorship. For the purposes | |
| 45 | of this License, Derivative Works shall not include works that remain | |
| 46 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 47 | the Work and Derivative Works thereof. | |
| 48 | ||
| 49 | "Contribution" shall mean any work of authorship, including | |
| 50 | the original version of the Work and any modifications or additions | |
| 51 | to that Work or Derivative Works thereof, that is intentionally | |
| 52 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 53 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 54 | the copyright owner. For the purposes of this definition, "submitted" | |
| 55 | means any form of electronic, verbal, or written communication sent | |
| 56 | to the Licensor or its representatives, including but not limited to | |
| 57 | communication on electronic mailing lists, source code control systems, | |
| 58 | and issue tracking systems that are managed by, or on behalf of, the | |
| 59 | Licensor for the purpose of discussing and improving the Work, but | |
| 60 | excluding communication that is conspicuously marked or otherwise | |
| 61 | designated in writing by the copyright owner as "Not a Contribution." | |
| 62 | ||
| 63 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 64 | on behalf of whom a Contribution has been received by Licensor and | |
| 65 | subsequently incorporated within the Work. | |
| 66 | ||
| 67 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 68 | this License, each Contributor hereby grants to You a perpetual, | |
| 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 70 | copyright license to reproduce, prepare Derivative Works of, | |
| 71 | publicly display, publicly perform, sublicense, and distribute the | |
| 72 | Work and such Derivative Works in Source or Object form. | |
| 73 | ||
| 74 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 75 | this License, each Contributor hereby grants to You a perpetual, | |
| 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 77 | (except as stated in this section) patent license to make, have made, | |
| 78 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 79 | where such license applies only to those patent claims licensable | |
| 80 | by such Contributor that are necessarily infringed by their | |
| 81 | Contribution(s) alone or by combination of their Contribution(s) | |
| 82 | with the Work to which such Contribution(s) was submitted. If You | |
| 83 | institute patent litigation against any entity (including a | |
| 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 85 | or a Contribution incorporated within the Work constitutes direct | |
| 86 | or contributory patent infringement, then any patent licenses | |
| 87 | granted to You under this License for that Work shall terminate | |
| 88 | as of the date such litigation is filed. | |
| 89 | ||
| 90 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 91 | Work or Derivative Works thereof in any medium, with or without | |
| 92 | modifications, and in Source or Object form, provided that You | |
| 93 | meet the following conditions: | |
| 94 | ||
| 95 | (a) You must give any other recipients of the Work or | |
| 96 | Derivative Works a copy of this License; and | |
| 97 | ||
| 98 | (b) You must cause any modified files to carry prominent notices | |
| 99 | stating that You changed the files; and | |
| 100 | ||
| 101 | (c) You must retain, in the Source form of any Derivative Works | |
| 102 | that You distribute, all copyright, patent, trademark, and | |
| 103 | attribution notices from the Source form of the Work, | |
| 104 | excluding those notices that do not pertain to any part of | |
| 105 | the Derivative Works; and | |
| 106 | ||
| 107 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 108 | distribution, then any Derivative Works that You distribute must | |
| 109 | include a readable copy of the attribution notices contained | |
| 110 | within such NOTICE file, excluding those notices that do not | |
| 111 | pertain to any part of the Derivative Works, in at least one | |
| 112 | of the following places: within a NOTICE text file distributed | |
| 113 | as part of the Derivative Works; within the Source form or | |
| 114 | documentation, if provided along with the Derivative Works; or, | |
| 115 | within a display generated by the Derivative Works, if and | |
| 116 | wherever such third-party notices normally appear. The contents | |
| 117 | of the NOTICE file are for informational purposes only and | |
| 118 | do not modify the License. You may add Your own attribution | |
| 119 | notices within Derivative Works that You distribute, alongside | |
| 120 | or as an addendum to the NOTICE text from the Work, provided | |
| 121 | that such additional attribution notices cannot be construed | |
| 122 | as modifying the License. | |
| 123 | ||
| 124 | You may add Your own copyright statement to Your modifications and | |
| 125 | may provide additional or different license terms and conditions | |
| 126 | for use, reproduction, or distribution of Your modifications, or | |
| 127 | for any such Derivative Works as a whole, provided Your use, | |
| 128 | reproduction, and distribution of the Work otherwise complies with | |
| 129 | the conditions stated in this License. | |
| 130 | ||
| 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 132 | any Contribution intentionally submitted for inclusion in the Work | |
| 133 | by You to the Licensor shall be under the terms and conditions of | |
| 134 | this License, without any additional terms or conditions. | |
| 135 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 136 | the terms of any separate license agreement you may have executed | |
| 137 | with Licensor regarding such Contributions. | |
| 138 | ||
| 139 | 6. Trademarks. This License does not grant permission to use the trade | |
| 140 | names, trademarks, service marks, or product names of the Licensor, | |
| 141 | except as required for reasonable and customary use in describing the | |
| 142 | origin of the Work and reproducing the content of the NOTICE file. | |
| 143 | ||
| 144 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 145 | agreed to in writing, Licensor provides the Work (and each | |
| 146 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 148 | implied, including, without limitation, any warranties or conditions | |
| 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 150 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 151 | appropriateness of using or redistributing the Work and assume any | |
| 152 | risks associated with Your exercise of permissions under this License. | |
| 153 | ||
| 154 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 155 | whether in tort (including negligence), contract, or otherwise, | |
| 156 | unless required by applicable law (such as deliberate and grossly | |
| 157 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 158 | liable to You for damages, including any direct, indirect, special, | |
| 159 | incidental, or consequential damages of any character arising as a | |
| 160 | result of this License or out of the use or inability to use the | |
| 161 | Work (including but not limited to damages for loss of goodwill, | |
| 162 | work stoppage, computer failure or malfunction, or any and all | |
| 163 | other commercial damages or losses), even if such Contributor | |
| 164 | has been advised of the possibility of such damages. | |
| 165 | ||
| 166 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 167 | the Work or Derivative Works thereof, You may choose to offer, | |
| 168 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 169 | or other liability obligations and/or rights consistent with this | |
| 170 | License. However, in accepting such obligations, You may act only | |
| 171 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 172 | of any other Contributor, and only if You agree to indemnify, | |
| 173 | defend, and hold each Contributor harmless for any liability | |
| 174 | incurred by, or claims asserted against, such Contributor by reason | |
| 175 | of your accepting any such warranty or additional liability. | |
| 176 | ||
| 177 | END OF TERMS AND CONDITIONS | |
| 178 | ||
| 179 | APPENDIX: How to apply the Apache License to your work. | |
| 180 | ||
| 181 | To apply the Apache License to your work, attach the following | |
| 182 | boilerplate notice, with the fields enclosed by brackets "[]" | |
| 183 | replaced with your own identifying information. (Don't include | |
| 184 | the brackets!) The text should be enclosed in the appropriate | |
| 185 | comment syntax for the file format. We also recommend that a | |
| 186 | file or class name and description of purpose be included on the | |
| 187 | same "printed page" as the copyright notice for easier | |
| 188 | identification within third-party archives. | |
| 189 | ||
| 190 | Copyright [yyyy] [name of copyright owner] | |
| 191 | ||
| 192 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 193 | you may not use this file except in compliance with the License. | |
| 194 | You may obtain a copy of the License at | |
| 195 | ||
| 196 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 197 | ||
| 198 | Unless required by applicable law or agreed to in writing, software | |
| 199 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 201 | See the License for the specific language governing permissions and | |
| 202 | limitations under the License. | |
| 203 |
| 1 | MIT License | |
| 2 | ||
| 3 | Copyright (c) 2019 Raul Garcia | |
| 4 | ||
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | of this software and associated documentation files (the "Software"), to deal | |
| 7 | in the Software without restriction, including without limitation the rights | |
| 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | copies of the Software, and to permit persons to whom the Software is | |
| 10 | furnished to do so, subject to the following conditions: | |
| 11 | ||
| 12 | The above copyright notice and this permission notice shall be included in all | |
| 13 | copies or substantial portions of the Software. | |
| 14 | ||
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | SOFTWARE. | |
| 1 | 22 |
| 1 | Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |
| 2 | ||
| 3 | The contents of this file are subject to the Mozilla Public License Version | |
| 4 | 1.1 (the "License"); you may not use this file except in compliance with | |
| 5 | the License. You may obtain a copy of the License at | |
| 6 | http://www.mozilla.org/MPL/ | |
| 7 | ||
| 8 | Software distributed under the License is distributed on an "AS IS" basis, | |
| 9 | WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
| 10 | for the specific language governing rights and limitations under the | |
| 11 | License. | |
| 12 | ||
| 13 | The Original Code is Mozilla Universal charset detector code. | |
| 14 | ||
| 15 | The Initial Developer of the Original Code is | |
| 16 | Netscape Communications Corporation. | |
| 17 | Portions created by the Initial Developer are Copyright (C) 2001 | |
| 18 | the Initial Developer. All Rights Reserved. | |
| 19 | ||
| 20 | Contributor(s): | |
| 21 | Shy Shalom <shooshX@gmail.com> | |
| 22 | Kohei TAKETA <k-tak@void.in> (Java port) | |
| 23 | ||
| 24 | Alternatively, the contents of this file may be used under the terms of | |
| 25 | either the GNU General Public License Version 2 or later (the "GPL"), or | |
| 26 | the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | |
| 27 | in which case the provisions of the GPL or the LGPL are applicable instead | |
| 28 | of those above. If you wish to allow use of your version of this file only | |
| 29 | under the terms of either the GPL or the LGPL, and not to allow others to | |
| 30 | use your version of this file under the terms of the MPL, indicate your | |
| 31 | decision by deleting the provisions above and replace them with the notice | |
| 32 | and other provisions required by the GPL or the LGPL. If you do not delete | |
| 33 | the provisions above, a recipient may use your version of this file under | |
| 34 | the terms of any one of the MPL, the GPL or the LGPL. | |
| 35 | ||
| 1 | 36 |
| 1 | Copyright (c) 2015 Karl Tauber <karl@jformdesigner.com> | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright | |
| 8 | notice, this list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright | |
| 11 | notice, this list of conditions and the following disclaimer in the | |
| 12 | documentation and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 25 |
| 1 | Copyright (c) 2000 Mikael Grev | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions | |
| 6 | are met: | |
| 7 | 1. Redistributions of source code must retain the above copyright | |
| 8 | notice, this list of conditions and the following disclaimer. | |
| 9 | 2. Redistributions in binary form must reproduce the above copyright | |
| 10 | notice, this list of conditions and the following disclaimer in the | |
| 11 | documentation and/or other materials provided with the distribution. | |
| 12 | 3. The name of the author may not be used to endorse or promote products | |
| 13 | derived from this software without specific prior written permission. | |
| 14 | ||
| 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
| 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
| 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
| 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
| 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 | ||
| 1 | 26 |
| 1 | Apache License | |
| 2 | Version 2.0, January 2004 | |
| 3 | http://www.apache.org/licenses/ | |
| 4 | ||
| 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 6 | ||
| 7 | 1. Definitions. | |
| 8 | ||
| 9 | "License" shall mean the terms and conditions for use, reproduction, | |
| 10 | and distribution as defined by Sections 1 through 9 of this document. | |
| 11 | ||
| 12 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 13 | the copyright owner that is granting the License. | |
| 14 | ||
| 15 | "Legal Entity" shall mean the union of the acting entity and all | |
| 16 | other entities that control, are controlled by, or are under common | |
| 17 | control with that entity. For the purposes of this definition, | |
| 18 | "control" means (i) the power, direct or indirect, to cause the | |
| 19 | direction or management of such entity, whether by contract or | |
| 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 21 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 22 | ||
| 23 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 24 | exercising permissions granted by this License. | |
| 25 | ||
| 26 | "Source" form shall mean the preferred form for making modifications, | |
| 27 | including but not limited to software source code, documentation | |
| 28 | source, and configuration files. | |
| 29 | ||
| 30 | "Object" form shall mean any form resulting from mechanical | |
| 31 | transformation or translation of a Source form, including but | |
| 32 | not limited to compiled object code, generated documentation, | |
| 33 | and conversions to other media types. | |
| 34 | ||
| 35 | "Work" shall mean the work of authorship, whether in Source or | |
| 36 | Object form, made available under the License, as indicated by a | |
| 37 | copyright notice that is included in or attached to the work | |
| 38 | (an example is provided in the Appendix below). | |
| 39 | ||
| 40 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 41 | form, that is based on (or derived from) the Work and for which the | |
| 42 | editorial revisions, annotations, elaborations, or other modifications | |
| 43 | represent, as a whole, an original work of authorship. For the purposes | |
| 44 | of this License, Derivative Works shall not include works that remain | |
| 45 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 46 | the Work and Derivative Works thereof. | |
| 47 | ||
| 48 | "Contribution" shall mean any work of authorship, including | |
| 49 | the original version of the Work and any modifications or additions | |
| 50 | to that Work or Derivative Works thereof, that is intentionally | |
| 51 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 52 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 53 | the copyright owner. For the purposes of this definition, "submitted" | |
| 54 | means any form of electronic, verbal, or written communication sent | |
| 55 | to the Licensor or its representatives, including but not limited to | |
| 56 | communication on electronic mailing lists, source code control systems, | |
| 57 | and issue tracking systems that are managed by, or on behalf of, the | |
| 58 | Licensor for the purpose of discussing and improving the Work, but | |
| 59 | excluding communication that is conspicuously marked or otherwise | |
| 60 | designated in writing by the copyright owner as "Not a Contribution." | |
| 61 | ||
| 62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 63 | on behalf of whom a Contribution has been received by Licensor and | |
| 64 | subsequently incorporated within the Work. | |
| 65 | ||
| 66 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 67 | this License, each Contributor hereby grants to You a perpetual, | |
| 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 69 | copyright license to reproduce, prepare Derivative Works of, | |
| 70 | publicly display, publicly perform, sublicense, and distribute the | |
| 71 | Work and such Derivative Works in Source or Object form. | |
| 72 | ||
| 73 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 74 | this License, each Contributor hereby grants to You a perpetual, | |
| 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 76 | (except as stated in this section) patent license to make, have made, | |
| 77 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 78 | where such license applies only to those patent claims licensable | |
| 79 | by such Contributor that are necessarily infringed by their | |
| 80 | Contribution(s) alone or by combination of their Contribution(s) | |
| 81 | with the Work to which such Contribution(s) was submitted. If You | |
| 82 | institute patent litigation against any entity (including a | |
| 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 84 | or a Contribution incorporated within the Work constitutes direct | |
| 85 | or contributory patent infringement, then any patent licenses | |
| 86 | granted to You under this License for that Work shall terminate | |
| 87 | as of the date such litigation is filed. | |
| 88 | ||
| 89 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 90 | Work or Derivative Works thereof in any medium, with or without | |
| 91 | modifications, and in Source or Object form, provided that You | |
| 92 | meet the following conditions: | |
| 93 | ||
| 94 | (a) You must give any other recipients of the Work or | |
| 95 | Derivative Works a copy of this License; and | |
| 96 | ||
| 97 | (b) You must cause any modified files to carry prominent notices | |
| 98 | stating that You changed the files; and | |
| 99 | ||
| 100 | (c) You must retain, in the Source form of any Derivative Works | |
| 101 | that You distribute, all copyright, patent, trademark, and | |
| 102 | attribution notices from the Source form of the Work, | |
| 103 | excluding those notices that do not pertain to any part of | |
| 104 | the Derivative Works; and | |
| 105 | ||
| 106 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 107 | distribution, then any Derivative Works that You distribute must | |
| 108 | include a readable copy of the attribution notices contained | |
| 109 | within such NOTICE file, excluding those notices that do not | |
| 110 | pertain to any part of the Derivative Works, in at least one | |
| 111 | of the following places: within a NOTICE text file distributed | |
| 112 | as part of the Derivative Works; within the Source form or | |
| 113 | documentation, if provided along with the Derivative Works; or, | |
| 114 | within a display generated by the Derivative Works, if and | |
| 115 | wherever such third-party notices normally appear. The contents | |
| 116 | of the NOTICE file are for informational purposes only and | |
| 117 | do not modify the License. You may add Your own attribution | |
| 118 | notices within Derivative Works that You distribute, alongside | |
| 119 | or as an addendum to the NOTICE text from the Work, provided | |
| 120 | that such additional attribution notices cannot be construed | |
| 121 | as modifying the License. | |
| 122 | ||
| 123 | You may add Your own copyright statement to Your modifications and | |
| 124 | may provide additional or different license terms and conditions | |
| 125 | for use, reproduction, or distribution of Your modifications, or | |
| 126 | for any such Derivative Works as a whole, provided Your use, | |
| 127 | reproduction, and distribution of the Work otherwise complies with | |
| 128 | the conditions stated in this License. | |
| 129 | ||
| 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 131 | any Contribution intentionally submitted for inclusion in the Work | |
| 132 | by You to the Licensor shall be under the terms and conditions of | |
| 133 | this License, without any additional terms or conditions. | |
| 134 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 135 | the terms of any separate license agreement you may have executed | |
| 136 | with Licensor regarding such Contributions. | |
| 137 | ||
| 138 | 6. Trademarks. This License does not grant permission to use the trade | |
| 139 | names, trademarks, service marks, or product names of the Licensor, | |
| 140 | except as required for reasonable and customary use in describing the | |
| 141 | origin of the Work and reproducing the content of the NOTICE file. | |
| 142 | ||
| 143 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 144 | agreed to in writing, Licensor provides the Work (and each | |
| 145 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 147 | implied, including, without limitation, any warranties or conditions | |
| 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 149 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 150 | appropriateness of using or redistributing the Work and assume any | |
| 151 | risks associated with Your exercise of permissions under this License. | |
| 152 | ||
| 153 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 154 | whether in tort (including negligence), contract, or otherwise, | |
| 155 | unless required by applicable law (such as deliberate and grossly | |
| 156 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 157 | liable to You for damages, including any direct, indirect, special, | |
| 158 | incidental, or consequential damages of any character arising as a | |
| 159 | result of this License or out of the use or inability to use the | |
| 160 | Work (including but not limited to damages for loss of goodwill, | |
| 161 | work stoppage, computer failure or malfunction, or any and all | |
| 162 | other commercial damages or losses), even if such Contributor | |
| 163 | has been advised of the possibility of such damages. | |
| 164 | ||
| 165 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 166 | the Work or Derivative Works thereof, You may choose to offer, | |
| 167 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 168 | or other liability obligations and/or rights consistent with this | |
| 169 | License. However, in accepting such obligations, You may act only | |
| 170 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 171 | of any other Contributor, and only if You agree to indemnify, | |
| 172 | defend, and hold each Contributor harmless for any liability | |
| 173 | incurred by, or claims asserted against, such Contributor by reason | |
| 174 | of your accepting any such warranty or additional liability. | |
| 175 | ||
| 176 | END OF TERMS AND CONDITIONS | |
| 177 | ||
| 178 | APPENDIX: How to apply the Apache License to your work. | |
| 179 | ||
| 180 | To apply the Apache License to your work, attach the following | |
| 181 | boilerplate notice, with the fields enclosed by brackets "{}" | |
| 182 | replaced with your own identifying information. (Don't include | |
| 183 | the brackets!) The text should be enclosed in the appropriate | |
| 184 | comment syntax for the file format. We also recommend that a | |
| 185 | file or class name and description of purpose be included on the | |
| 186 | same "printed page" as the copyright notice for easier | |
| 187 | identification within third-party archives. | |
| 188 | ||
| 189 | Copyright {yyyy} {name of copyright owner} | |
| 190 | ||
| 191 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 192 | you may not use this file except in compliance with the License. | |
| 193 | You may obtain a copy of the License at | |
| 194 | ||
| 195 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 196 | ||
| 197 | Unless required by applicable law or agreed to in writing, software | |
| 198 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 200 | See the License for the specific language governing permissions and | |
| 201 | limitations under the License. | |
| 1 | 202 |
| 1 | Copyright (c) 2013-2014, Tomas Mikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
| 5 | ||
| 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
| 7 | ||
| 8 | 2. 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. | |
| 9 | ||
| 10 | 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. | |
| 1 | 11 |
| 1 | GNU GENERAL PUBLIC LICENSE | |
| 2 | Version 2, June 1991 | |
| 3 | ||
| 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. | |
| 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 6 | Everyone is permitted to copy and distribute verbatim copies | |
| 7 | of this license document, but changing it is not allowed. | |
| 8 | ||
| 9 | Preamble | |
| 10 | ||
| 11 | The licenses for most software are designed to take away your | |
| 12 | freedom to share and change it. By contrast, the GNU General Public | |
| 13 | License is intended to guarantee your freedom to share and change free | |
| 14 | software--to make sure the software is free for all its users. This | |
| 15 | General Public License applies to most of the Free Software | |
| 16 | Foundation's software and to any other program whose authors commit to | |
| 17 | using it. (Some other Free Software Foundation software is covered by | |
| 18 | the GNU Library General Public License instead.) You can apply it to | |
| 19 | your programs, too. | |
| 20 | ||
| 21 | When we speak of free software, we are referring to freedom, not | |
| 22 | price. Our General Public Licenses are designed to make sure that you | |
| 23 | have the freedom to distribute copies of free software (and charge for | |
| 24 | this service if you wish), that you receive source code or can get it | |
| 25 | if you want it, that you can change the software or use pieces of it | |
| 26 | in new free programs; and that you know you can do these things. | |
| 27 | ||
| 28 | To protect your rights, we need to make restrictions that forbid | |
| 29 | anyone to deny you these rights or to ask you to surrender the rights. | |
| 30 | These restrictions translate to certain responsibilities for you if you | |
| 31 | distribute copies of the software, or if you modify it. | |
| 32 | ||
| 33 | For example, if you distribute copies of such a program, whether | |
| 34 | gratis or for a fee, you must give the recipients all the rights that | |
| 35 | you have. You must make sure that they, too, receive or can get the | |
| 36 | source code. And you must show them these terms so they know their | |
| 37 | rights. | |
| 38 | ||
| 39 | We protect your rights with two steps: (1) copyright the software, and | |
| 40 | (2) offer you this license which gives you legal permission to copy, | |
| 41 | distribute and/or modify the software. | |
| 42 | ||
| 43 | Also, for each author's protection and ours, we want to make certain | |
| 44 | that everyone understands that there is no warranty for this free | |
| 45 | software. If the software is modified by someone else and passed on, we | |
| 46 | want its recipients to know that what they have is not the original, so | |
| 47 | that any problems introduced by others will not reflect on the original | |
| 48 | authors' reputations. | |
| 49 | ||
| 50 | Finally, any free program is threatened constantly by software | |
| 51 | patents. We wish to avoid the danger that redistributors of a free | |
| 52 | program will individually obtain patent licenses, in effect making the | |
| 53 | program proprietary. To prevent this, we have made it clear that any | |
| 54 | patent must be licensed for everyone's free use or not licensed at all. | |
| 55 | ||
| 56 | The precise terms and conditions for copying, distribution and | |
| 57 | modification follow. | |
| 58 | ||
| 59 | GNU GENERAL PUBLIC LICENSE | |
| 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
| 61 | ||
| 62 | 0. This License applies to any program or other work which contains | |
| 63 | a notice placed by the copyright holder saying it may be distributed | |
| 64 | under the terms of this General Public License. The "Program", below, | |
| 65 | refers to any such program or work, and a "work based on the Program" | |
| 66 | means either the Program or any derivative work under copyright law: | |
| 67 | that is to say, a work containing the Program or a portion of it, | |
| 68 | either verbatim or with modifications and/or translated into another | |
| 69 | language. (Hereinafter, translation is included without limitation in | |
| 70 | the term "modification".) Each licensee is addressed as "you". | |
| 71 | ||
| 72 | Activities other than copying, distribution and modification are not | |
| 73 | covered by this License; they are outside its scope. The act of | |
| 74 | running the Program is not restricted, and the output from the Program | |
| 75 | is covered only if its contents constitute a work based on the | |
| 76 | Program (independent of having been made by running the Program). | |
| 77 | Whether that is true depends on what the Program does. | |
| 78 | ||
| 79 | 1. You may copy and distribute verbatim copies of the Program's | |
| 80 | source code as you receive it, in any medium, provided that you | |
| 81 | conspicuously and appropriately publish on each copy an appropriate | |
| 82 | copyright notice and disclaimer of warranty; keep intact all the | |
| 83 | notices that refer to this License and to the absence of any warranty; | |
| 84 | and give any other recipients of the Program a copy of this License | |
| 85 | along with the Program. | |
| 86 | ||
| 87 | You may charge a fee for the physical act of transferring a copy, and | |
| 88 | you may at your option offer warranty protection in exchange for a fee. | |
| 89 | ||
| 90 | 2. You may modify your copy or copies of the Program or any portion | |
| 91 | of it, thus forming a work based on the Program, and copy and | |
| 92 | distribute such modifications or work under the terms of Section 1 | |
| 93 | above, provided that you also meet all of these conditions: | |
| 94 | ||
| 95 | a) You must cause the modified files to carry prominent notices | |
| 96 | stating that you changed the files and the date of any change. | |
| 97 | ||
| 98 | b) You must cause any work that you distribute or publish, that in | |
| 99 | whole or in part contains or is derived from the Program or any | |
| 100 | part thereof, to be licensed as a whole at no charge to all third | |
| 101 | parties under the terms of this License. | |
| 102 | ||
| 103 | c) If the modified program normally reads commands interactively | |
| 104 | when run, you must cause it, when started running for such | |
| 105 | interactive use in the most ordinary way, to print or display an | |
| 106 | announcement including an appropriate copyright notice and a | |
| 107 | notice that there is no warranty (or else, saying that you provide | |
| 108 | a warranty) and that users may redistribute the program under | |
| 109 | these conditions, and telling the user how to view a copy of this | |
| 110 | License. (Exception: if the Program itself is interactive but | |
| 111 | does not normally print such an announcement, your work based on | |
| 112 | the Program is not required to print an announcement.) | |
| 113 | ||
| 114 | These requirements apply to the modified work as a whole. If | |
| 115 | identifiable sections of that work are not derived from the Program, | |
| 116 | and can be reasonably considered independent and separate works in | |
| 117 | themselves, then this License, and its terms, do not apply to those | |
| 118 | sections when you distribute them as separate works. But when you | |
| 119 | distribute the same sections as part of a whole which is a work based | |
| 120 | on the Program, the distribution of the whole must be on the terms of | |
| 121 | this License, whose permissions for other licensees extend to the | |
| 122 | entire whole, and thus to each and every part regardless of who wrote it. | |
| 123 | ||
| 124 | Thus, it is not the intent of this section to claim rights or contest | |
| 125 | your rights to work written entirely by you; rather, the intent is to | |
| 126 | exercise the right to control the distribution of derivative or | |
| 127 | collective works based on the Program. | |
| 128 | ||
| 129 | In addition, mere aggregation of another work not based on the Program | |
| 130 | with the Program (or with a work based on the Program) on a volume of | |
| 131 | a storage or distribution medium does not bring the other work under | |
| 132 | the scope of this License. | |
| 133 | ||
| 134 | 3. You may copy and distribute the Program (or a work based on it, | |
| 135 | under Section 2) in object code or executable form under the terms of | |
| 136 | Sections 1 and 2 above provided that you also do one of the following: | |
| 137 | ||
| 138 | a) Accompany it with the complete corresponding machine-readable | |
| 139 | source code, which must be distributed under the terms of Sections | |
| 140 | 1 and 2 above on a medium customarily used for software interchange; or, | |
| 141 | ||
| 142 | b) Accompany it with a written offer, valid for at least three | |
| 143 | years, to give any third party, for a charge no more than your | |
| 144 | cost of physically performing source distribution, a complete | |
| 145 | machine-readable copy of the corresponding source code, to be | |
| 146 | distributed under the terms of Sections 1 and 2 above on a medium | |
| 147 | customarily used for software interchange; or, | |
| 148 | ||
| 149 | c) Accompany it with the information you received as to the offer | |
| 150 | to distribute corresponding source code. (This alternative is | |
| 151 | allowed only for noncommercial distribution and only if you | |
| 152 | received the program in object code or executable form with such | |
| 153 | an offer, in accord with Subsection b above.) | |
| 154 | ||
| 155 | The source code for a work means the preferred form of the work for | |
| 156 | making modifications to it. For an executable work, complete source | |
| 157 | code means all the source code for all modules it contains, plus any | |
| 158 | associated interface definition files, plus the scripts used to | |
| 159 | control compilation and installation of the executable. However, as a | |
| 160 | special exception, the source code distributed need not include | |
| 161 | anything that is normally distributed (in either source or binary | |
| 162 | form) with the major components (compiler, kernel, and so on) of the | |
| 163 | operating system on which the executable runs, unless that component | |
| 164 | itself accompanies the executable. | |
| 165 | ||
| 166 | If distribution of executable or object code is made by offering | |
| 167 | access to copy from a designated place, then offering equivalent | |
| 168 | access to copy the source code from the same place counts as | |
| 169 | distribution of the source code, even though third parties are not | |
| 170 | compelled to copy the source along with the object code. | |
| 171 | ||
| 172 | 4. You may not copy, modify, sublicense, or distribute the Program | |
| 173 | except as expressly provided under this License. Any attempt | |
| 174 | otherwise to copy, modify, sublicense or distribute the Program is | |
| 175 | void, and will automatically terminate your rights under this License. | |
| 176 | However, parties who have received copies, or rights, from you under | |
| 177 | this License will not have their licenses terminated so long as such | |
| 178 | parties remain in full compliance. | |
| 179 | ||
| 180 | 5. You are not required to accept this License, since you have not | |
| 181 | signed it. However, nothing else grants you permission to modify or | |
| 182 | distribute the Program or its derivative works. These actions are | |
| 183 | prohibited by law if you do not accept this License. Therefore, by | |
| 184 | modifying or distributing the Program (or any work based on the | |
| 185 | Program), you indicate your acceptance of this License to do so, and | |
| 186 | all its terms and conditions for copying, distributing or modifying | |
| 187 | the Program or works based on it. | |
| 188 | ||
| 189 | 6. Each time you redistribute the Program (or any work based on the | |
| 190 | Program), the recipient automatically receives a license from the | |
| 191 | original licensor to copy, distribute or modify the Program subject to | |
| 192 | these terms and conditions. You may not impose any further | |
| 193 | restrictions on the recipients' exercise of the rights granted herein. | |
| 194 | You are not responsible for enforcing compliance by third parties to | |
| 195 | this License. | |
| 196 | ||
| 197 | 7. If, as a consequence of a court judgment or allegation of patent | |
| 198 | infringement or for any other reason (not limited to patent issues), | |
| 199 | conditions are imposed on you (whether by court order, agreement or | |
| 200 | otherwise) that contradict the conditions of this License, they do not | |
| 201 | excuse you from the conditions of this License. If you cannot | |
| 202 | distribute so as to satisfy simultaneously your obligations under this | |
| 203 | License and any other pertinent obligations, then as a consequence you | |
| 204 | may not distribute the Program at all. For example, if a patent | |
| 205 | license would not permit royalty-free redistribution of the Program by | |
| 206 | all those who receive copies directly or indirectly through you, then | |
| 207 | the only way you could satisfy both it and this License would be to | |
| 208 | refrain entirely from distribution of the Program. | |
| 209 | ||
| 210 | If any portion of this section is held invalid or unenforceable under | |
| 211 | any particular circumstance, the balance of the section is intended to | |
| 212 | apply and the section as a whole is intended to apply in other | |
| 213 | circumstances. | |
| 214 | ||
| 215 | It is not the purpose of this section to induce you to infringe any | |
| 216 | patents or other property right claims or to contest validity of any | |
| 217 | such claims; this section has the sole purpose of protecting the | |
| 218 | integrity of the free software distribution system, which is | |
| 219 | implemented by public license practices. Many people have made | |
| 220 | generous contributions to the wide range of software distributed | |
| 221 | through that system in reliance on consistent application of that | |
| 222 | system; it is up to the author/donor to decide if he or she is willing | |
| 223 | to distribute software through any other system and a licensee cannot | |
| 224 | impose that choice. | |
| 225 | ||
| 226 | This section is intended to make thoroughly clear what is believed to | |
| 227 | be a consequence of the rest of this License. | |
| 228 | ||
| 229 | 8. If the distribution and/or use of the Program is restricted in | |
| 230 | certain countries either by patents or by copyrighted interfaces, the | |
| 231 | original copyright holder who places the Program under this License | |
| 232 | may add an explicit geographical distribution limitation excluding | |
| 233 | those countries, so that distribution is permitted only in or among | |
| 234 | countries not thus excluded. In such case, this License incorporates | |
| 235 | the limitation as if written in the body of this License. | |
| 236 | ||
| 237 | 9. The Free Software Foundation may publish revised and/or new versions | |
| 238 | of the General Public License from time to time. Such new versions will | |
| 239 | be similar in spirit to the present version, but may differ in detail to | |
| 240 | address new problems or concerns. | |
| 241 | ||
| 242 | Each version is given a distinguishing version number. If the Program | |
| 243 | specifies a version number of this License which applies to it and "any | |
| 244 | later version", you have the option of following the terms and conditions | |
| 245 | either of that version or of any later version published by the Free | |
| 246 | Software Foundation. If the Program does not specify a version number of | |
| 247 | this License, you may choose any version ever published by the Free Software | |
| 248 | Foundation. | |
| 249 | ||
| 250 | 10. If you wish to incorporate parts of the Program into other free | |
| 251 | programs whose distribution conditions are different, write to the author | |
| 252 | to ask for permission. For software which is copyrighted by the Free | |
| 253 | Software Foundation, write to the Free Software Foundation; we sometimes | |
| 254 | make exceptions for this. Our decision will be guided by the two goals | |
| 255 | of preserving the free status of all derivatives of our free software and | |
| 256 | of promoting the sharing and reuse of software generally. | |
| 257 | ||
| 258 | NO WARRANTY | |
| 259 | ||
| 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |
| 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |
| 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |
| 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |
| 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
| 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |
| 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |
| 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |
| 268 | REPAIR OR CORRECTION. | |
| 269 | ||
| 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |
| 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |
| 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |
| 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |
| 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |
| 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |
| 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |
| 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |
| 278 | POSSIBILITY OF SUCH DAMAGES. | |
| 279 | ||
| 280 | END OF TERMS AND CONDITIONS | |
| 281 | ||
| 282 | How to Apply These Terms to Your New Programs | |
| 283 | ||
| 284 | If you develop a new program, and you want it to be of the greatest | |
| 285 | possible use to the public, the best way to achieve this is to make it | |
| 286 | free software which everyone can redistribute and change under these terms. | |
| 287 | ||
| 288 | To do so, attach the following notices to the program. It is safest | |
| 289 | to attach them to the start of each source file to most effectively | |
| 290 | convey the exclusion of warranty; and each file should have at least | |
| 291 | the "copyright" line and a pointer to where the full notice is found. | |
| 292 | ||
| 293 | <one line to give the program's name and a brief idea of what it does.> | |
| 294 | Copyright (C) <year> <name of author> | |
| 295 | ||
| 296 | This program is free software; you can redistribute it and/or modify | |
| 297 | it under the terms of the GNU General Public License as published by | |
| 298 | the Free Software Foundation; either version 2 of the License, or | |
| 299 | (at your option) any later version. | |
| 300 | ||
| 301 | This program is distributed in the hope that it will be useful, | |
| 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 304 | GNU General Public License for more details. | |
| 305 | ||
| 306 | You should have received a copy of the GNU General Public License | |
| 307 | along with this program; if not, write to the Free Software | |
| 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 309 | ||
| 310 | ||
| 311 | Also add information on how to contact you by electronic and paper mail. | |
| 312 | ||
| 313 | If the program is interactive, make it output a short notice like this | |
| 314 | when it starts in an interactive mode: | |
| 315 | ||
| 316 | Gnomovision version 69, Copyright (C) year name of author | |
| 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |
| 318 | This is free software, and you are welcome to redistribute it | |
| 319 | under certain conditions; type `show c' for details. | |
| 320 | ||
| 321 | The hypothetical commands `show w' and `show c' should show the appropriate | |
| 322 | parts of the General Public License. Of course, the commands you use may | |
| 323 | be called something other than `show w' and `show c'; they could even be | |
| 324 | mouse-clicks or menu items--whatever suits your program. | |
| 325 | ||
| 326 | You should also get your employer (if you work as a programmer) or your | |
| 327 | school, if any, to sign a "copyright disclaimer" for the program, if | |
| 328 | necessary. Here is a sample; alter the names: | |
| 329 | ||
| 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |
| 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. | |
| 332 | ||
| 333 | <signature of Ty Coon>, 1 April 1989 | |
| 334 | Ty Coon, President of Vice | |
| 335 | ||
| 336 | This General Public License does not permit incorporating your program into | |
| 337 | proprietary programs. If your program is a subroutine library, you may | |
| 338 | consider it more useful to permit linking proprietary applications with the | |
| 339 | library. If this is what you want to do, use the GNU Library General | |
| 340 | Public License instead of this License. | |
| 1 | 341 |
| 1 | Copyright (c) 2013-2017, Tomas Mikula and contributors | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
| 5 | ||
| 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
| 7 | ||
| 8 | 2. 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. | |
| 9 | ||
| 10 | 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. | |
| 1 | 11 |
| 1 | Mozilla Public License | |
| 2 | Version 2.0 | |
| 3 | ||
| 4 | 1. Definitions | |
| 5 | ||
| 6 | 1.1. “Contributor” | |
| 7 | means each individual or legal entity that creates, contributes | |
| 8 | to the creation of, or owns Covered Software. | |
| 9 | ||
| 10 | 1.2. “Contributor Version” | |
| 11 | means the combination of the Contributions of others (if any) | |
| 12 | used by a Contributor and that particular Contributor’s | |
| 13 | Contribution. | |
| 14 | ||
| 15 | 1.3. “Contribution” | |
| 16 | means Covered Software of a particular Contributor. | |
| 17 | ||
| 18 | 1.4. “Covered Software” | |
| 19 | means Source Code Form to which the initial Contributor has | |
| 20 | attached the notice in Exhibit A, the Executable Form of such | |
| 21 | Source Code Form, and Modifications of such Source Code Form, in | |
| 22 | each case including portions thereof. | |
| 23 | ||
| 24 | 1.5. “Incompatible With Secondary Licenses” | |
| 25 | means | |
| 26 | ||
| 27 | a. that the initial Contributor has attached the notice described | |
| 28 | in Exhibit B to the Covered Software; or | |
| 29 | b. that the Covered Software was made available under the terms | |
| 30 | of version 1.1 or earlier of the License, but not also under | |
| 31 | the terms of a Secondary License. | |
| 32 | ||
| 33 | 1.6. “Executable Form” | |
| 34 | means any form of the work other than Source Code Form. | |
| 35 | ||
| 36 | 1.7. “Larger Work” | |
| 37 | means a work that combines Covered Software with other material, | |
| 38 | in a separate file or files, that is not Covered Software. | |
| 39 | ||
| 40 | 1.8. “License” | |
| 41 | means this document. | |
| 42 | ||
| 43 | 1.9. “Licensable” | |
| 44 | means having the right to grant, to the maximum extent possible, | |
| 45 | whether at the time of the initial grant or subsequently, any | |
| 46 | and all of the rights conveyed by this License. | |
| 47 | ||
| 48 | 1.10. “Modifications” | |
| 49 | means any of the following: | |
| 50 | ||
| 51 | a. any file in Source Code Form that results from an addition to, | |
| 52 | deletion from, or modification of the contents of Covered | |
| 53 | Software; or | |
| 54 | b. any new file in Source Code Form that contains any Covered | |
| 55 | Software. | |
| 56 | ||
| 57 | 1.11. “Patent Claims” of a Contributor | |
| 58 | means any patent claim(s), including without limitation, method, | |
| 59 | process, and apparatus claims, in any patent Licensable by such | |
| 60 | Contributor that would be infringed, but for the grant of the | |
| 61 | License, by the making, using, selling, offering for sale, | |
| 62 | having made, import, or transfer of either its Contributions or | |
| 63 | its Contributor Version. | |
| 64 | ||
| 65 | 1.12. “Secondary License” | |
| 66 | means either the GNU General Public License, Version 2.0, the | |
| 67 | GNU Lesser General Public License, Version 2.1, the GNU Affero | |
| 68 | General Public License, Version 3.0, or any later versions of | |
| 69 | those licenses. | |
| 70 | ||
| 71 | 1.13. “Source Code Form” | |
| 72 | means the form of the work preferred for making modifications. | |
| 73 | ||
| 74 | 1.14. “You” (or “Your”) | |
| 75 | means an individual or a legal entity exercising rights under | |
| 76 | this License. For legal entities, “You” includes any entity that | |
| 77 | controls, is controlled by, or is under common control with You. | |
| 78 | For purposes of this definition, “control” means (a) the power, | |
| 79 | direct or indirect, to cause the direction or management of such | |
| 80 | entity, whether by contract or otherwise, or (b) ownership of | |
| 81 | more than fifty percent (50%) of the outstanding shares or | |
| 82 | beneficial ownership of such entity. | |
| 83 | ||
| 84 | 2. License Grants and Conditions | |
| 85 | ||
| 86 | 2.1. Grants | |
| 87 | ||
| 88 | Each Contributor hereby grants You a world-wide, royalty-free, | |
| 89 | non-exclusive license: | |
| 90 | a. under intellectual property rights (other than patent or trademark) | |
| 91 | Licensable by such Contributor to use, reproduce, make available, | |
| 92 | modify, display, perform, distribute, and otherwise exploit its | |
| 93 | Contributions, either on an unmodified basis, with Modifications, | |
| 94 | or as part of a Larger Work; and | |
| 95 | b. under Patent Claims of such Contributor to make, use, sell, offer | |
| 96 | for sale, have made, import, and otherwise transfer either its | |
| 97 | Contributions or its Contributor Version. | |
| 98 | ||
| 99 | 2.2. Effective Date | |
| 100 | ||
| 101 | The licenses granted in Section 2.1 with respect to any Contribution | |
| 102 | become effective for each Contribution on the date the Contributor | |
| 103 | first distributes such Contribution. | |
| 104 | ||
| 105 | 2.3. Limitations on Grant Scope | |
| 106 | ||
| 107 | The licenses granted in this Section 2 are the only rights granted | |
| 108 | under this License. No additional rights or licenses will be implied | |
| 109 | from the distribution or licensing of Covered Software under this | |
| 110 | License. Notwithstanding Section 2.1(b) above, no patent license is | |
| 111 | granted by a Contributor: | |
| 112 | a. for any code that a Contributor has removed from Covered Software; | |
| 113 | or | |
| 114 | b. for infringements caused by: (i) Your and any other third party’s | |
| 115 | modifications of Covered Software, or (ii) the combination of its | |
| 116 | Contributions with other software (except as part of its | |
| 117 | Contributor Version); or | |
| 118 | c. under Patent Claims infringed by Covered Software in the absence of | |
| 119 | its Contributions. | |
| 120 | ||
| 121 | This License does not grant any rights in the trademarks, service | |
| 122 | marks, or logos of any Contributor (except as may be necessary to | |
| 123 | comply with the notice requirements in Section 3.4). | |
| 124 | ||
| 125 | 2.4. Subsequent Licenses | |
| 126 | ||
| 127 | No Contributor makes additional grants as a result of Your choice to | |
| 128 | distribute the Covered Software under a subsequent version of this | |
| 129 | License (see Section 10.2) or under the terms of a Secondary License | |
| 130 | (if permitted under the terms of Section 3.3). | |
| 131 | ||
| 132 | 2.5. Representation | |
| 133 | ||
| 134 | Each Contributor represents that the Contributor believes its | |
| 135 | Contributions are its original creation(s) or it has sufficient rights | |
| 136 | to grant the rights to its Contributions conveyed by this License. | |
| 137 | ||
| 138 | 2.6. Fair Use | |
| 139 | ||
| 140 | This License is not intended to limit any rights You have under | |
| 141 | applicable copyright doctrines of fair use, fair dealing, or other | |
| 142 | equivalents. | |
| 143 | ||
| 144 | 2.7. Conditions | |
| 145 | ||
| 146 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | |
| 147 | in Section 2.1. | |
| 148 | ||
| 149 | 3. Responsibilities | |
| 150 | ||
| 151 | 3.1. Distribution of Source Form | |
| 152 | ||
| 153 | All distribution of Covered Software in Source Code Form, including any | |
| 154 | Modifications that You create or to which You contribute, must be under | |
| 155 | the terms of this License. You must inform recipients that the Source | |
| 156 | Code Form of the Covered Software is governed by the terms of this | |
| 157 | License, and how they can obtain a copy of this License. You may not | |
| 158 | attempt to alter or restrict the recipients’ rights in the Source Code | |
| 159 | Form. | |
| 160 | ||
| 161 | 3.2. Distribution of Executable Form | |
| 162 | ||
| 163 | If You distribute Covered Software in Executable Form then: | |
| 164 | a. such Covered Software must also be made available in Source Code | |
| 165 | Form, as described in Section 3.1, and You must inform recipients | |
| 166 | of the Executable Form how they can obtain a copy of such Source | |
| 167 | Code Form by reasonable means in a timely manner, at a charge no | |
| 168 | more than the cost of distribution to the recipient; and | |
| 169 | b. You may distribute such Executable Form under the terms of this | |
| 170 | License, or sublicense it under different terms, provided that the | |
| 171 | license for the Executable Form does not attempt to limit or alter | |
| 172 | the recipients’ rights in the Source Code Form under this License. | |
| 173 | ||
| 174 | 3.3. Distribution of a Larger Work | |
| 175 | ||
| 176 | You may create and distribute a Larger Work under terms of Your choice, | |
| 177 | provided that You also comply with the requirements of this License for | |
| 178 | the Covered Software. If the Larger Work is a combination of Covered | |
| 179 | Software with a work governed by one or more Secondary Licenses, and | |
| 180 | the Covered Software is not Incompatible With Secondary Licenses, this | |
| 181 | License permits You to additionally distribute such Covered Software | |
| 182 | under the terms of such Secondary License(s), so that the recipient of | |
| 183 | the Larger Work may, at their option, further distribute the Covered | |
| 184 | Software under the terms of either this License or such Secondary | |
| 185 | License(s). | |
| 186 | ||
| 187 | 3.4. Notices | |
| 188 | ||
| 189 | You may not remove or alter the substance of any license notices | |
| 190 | (including copyright notices, patent notices, disclaimers of warranty, | |
| 191 | or limitations of liability) contained within the Source Code Form of | |
| 192 | the Covered Software, except that You may alter any license notices to | |
| 193 | the extent required to remedy known factual inaccuracies. | |
| 194 | ||
| 195 | 3.5. Application of Additional Terms | |
| 196 | ||
| 197 | You may choose to offer, and to charge a fee for, warranty, support, | |
| 198 | indemnity or liability obligations to one or more recipients of Covered | |
| 199 | Software. However, You may do so only on Your own behalf, and not on | |
| 200 | behalf of any Contributor. You must make it absolutely clear that any | |
| 201 | such warranty, support, indemnity, or liability obligation is offered | |
| 202 | by You alone, and You hereby agree to indemnify every Contributor for | |
| 203 | any liability incurred by such Contributor as a result of warranty, | |
| 204 | support, indemnity or liability terms You offer. You may include | |
| 205 | additional disclaimers of warranty and limitations of liability | |
| 206 | specific to any jurisdiction. | |
| 207 | ||
| 208 | 4. Inability to Comply Due to Statute or Regulation | |
| 209 | ||
| 210 | If it is impossible for You to comply with any of the terms of this | |
| 211 | License with respect to some or all of the Covered Software due to | |
| 212 | statute, judicial order, or regulation then You must: (a) comply with | |
| 213 | the terms of this License to the maximum extent possible; and (b) | |
| 214 | describe the limitations and the code they affect. Such description | |
| 215 | must be placed in a text file included with all distributions of the | |
| 216 | Covered Software under this License. Except to the extent prohibited by | |
| 217 | statute or regulation, such description must be sufficiently detailed | |
| 218 | for a recipient of ordinary skill to be able to understand it. | |
| 219 | ||
| 220 | 5. Termination | |
| 221 | ||
| 222 | 5.1. The rights granted under this License will terminate automatically | |
| 223 | if You fail to comply with any of its terms. However, if You become | |
| 224 | compliant, then the rights granted under this License from a particular | |
| 225 | Contributor are reinstated (a) provisionally, unless and until such | |
| 226 | Contributor explicitly and finally terminates Your grants, and (b) on | |
| 227 | an ongoing basis, if such Contributor fails to notify You of the | |
| 228 | non-compliance by some reasonable means prior to 60 days after You have | |
| 229 | come back into compliance. Moreover, Your grants from a particular | |
| 230 | Contributor are reinstated on an ongoing basis if such Contributor | |
| 231 | notifies You of the non-compliance by some reasonable means, this is | |
| 232 | the first time You have received notice of non-compliance with this | |
| 233 | License from such Contributor, and You become compliant prior to 30 | |
| 234 | days after Your receipt of the notice. | |
| 235 | ||
| 236 | 5.2. If You initiate litigation against any entity by asserting a | |
| 237 | patent infringement claim (excluding declaratory judgment actions, | |
| 238 | counter-claims, and cross-claims) alleging that a Contributor Version | |
| 239 | directly or indirectly infringes any patent, then the rights granted to | |
| 240 | You by any and all Contributors for the Covered Software under | |
| 241 | Section 2.1 of this License shall terminate. | |
| 242 | ||
| 243 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | |
| 244 | end user license agreements (excluding distributors and resellers) | |
| 245 | which have been validly granted by You or Your distributors under this | |
| 246 | License prior to termination shall survive termination. | |
| 247 | ||
| 248 | 6. Disclaimer of Warranty | |
| 249 | ||
| 250 | Covered Software is provided under this License on an “as is” basis, | |
| 251 | without warranty of any kind, either expressed, implied, or statutory, | |
| 252 | including, without limitation, warranties that the Covered Software is | |
| 253 | free of defects, merchantable, fit for a particular purpose or | |
| 254 | non-infringing. The entire risk as to the quality and performance of | |
| 255 | the Covered Software is with You. Should any Covered Software prove | |
| 256 | defective in any respect, You (not any Contributor) assume the cost of | |
| 257 | any necessary servicing, repair, or correction. This disclaimer of | |
| 258 | warranty constitutes an essential part of this License. No use of any | |
| 259 | Covered Software is authorized under this License except under this | |
| 260 | disclaimer. | |
| 261 | ||
| 262 | 7. Limitation of Liability | |
| 263 | ||
| 264 | Under no circumstances and under no legal theory, whether tort | |
| 265 | (including negligence), contract, or otherwise, shall any Contributor, | |
| 266 | or anyone who distributes Covered Software as permitted above, be | |
| 267 | liable to You for any direct, indirect, special, incidental, or | |
| 268 | consequential damages of any character including, without limitation, | |
| 269 | damages for lost profits, loss of goodwill, work stoppage, computer | |
| 270 | failure or malfunction, or any and all other commercial damages or | |
| 271 | losses, even if such party shall have been informed of the possibility | |
| 272 | of such damages. This limitation of liability shall not apply to | |
| 273 | liability for death or personal injury resulting from such party’s | |
| 274 | negligence to the extent applicable law prohibits such limitation. Some | |
| 275 | jurisdictions do not allow the exclusion or limitation of incidental or | |
| 276 | consequential damages, so this exclusion and limitation may not apply | |
| 277 | to You. | |
| 278 | ||
| 279 | 8. Litigation | |
| 280 | ||
| 281 | Any litigation relating to this License may be brought only in the | |
| 282 | courts of a jurisdiction where the defendant maintains its principal | |
| 283 | place of business and such litigation shall be governed by laws of that | |
| 284 | jurisdiction, without reference to its conflict-of-law provisions. | |
| 285 | Nothing in this Section shall prevent a party’s ability to bring | |
| 286 | cross-claims or counter-claims. | |
| 287 | ||
| 288 | 9. Miscellaneous | |
| 289 | ||
| 290 | This License represents the complete agreement concerning the subject | |
| 291 | matter hereof. If any provision of this License is held to be | |
| 292 | unenforceable, such provision shall be reformed only to the extent | |
| 293 | necessary to make it enforceable. Any law or regulation which provides | |
| 294 | that the language of a contract shall be construed against the drafter | |
| 295 | shall not be used to construe this License against a Contributor. | |
| 296 | ||
| 297 | 10. Versions of the License | |
| 298 | ||
| 299 | 10.1. New Versions | |
| 300 | ||
| 301 | Mozilla Foundation is the license steward. Except as provided in | |
| 302 | Section 10.3, no one other than the license steward has the right to | |
| 303 | modify or publish new versions of this License. Each version will be | |
| 304 | given a distinguishing version number. | |
| 305 | ||
| 306 | 10.2. Effect of New Versions | |
| 307 | ||
| 308 | You may distribute the Covered Software under the terms of the version | |
| 309 | of the License under which You originally received the Covered | |
| 310 | Software, or under the terms of any subsequent version published by the | |
| 311 | license steward. | |
| 312 | ||
| 313 | 10.3. Modified Versions | |
| 314 | ||
| 315 | If you create software not governed by this License, and you want to | |
| 316 | create a new license for such software, you may create and use a | |
| 317 | modified version of this License if you rename the license and remove | |
| 318 | any references to the name of the license steward (except to note that | |
| 319 | such modified license differs from this License). | |
| 320 | ||
| 321 | 10.4. Distributing Source Code Form that is Incompatible With Secondary | |
| 322 | Licenses | |
| 323 | ||
| 324 | If You choose to distribute Source Code Form that is Incompatible With | |
| 325 | Secondary Licenses under the terms of this version of the License, the | |
| 326 | notice described in Exhibit B of this License must be attached. | |
| 327 | ||
| 328 | Exhibit A - Source Code Form License Notice | |
| 329 | ||
| 330 | This Source Code Form is subject to the terms of the Mozilla Public | |
| 331 | License, v. 2.0. If a copy of the MPL was not distributed with this | |
| 332 | file, You can obtain one at https://mozilla.org/MPL/2.0/. | |
| 333 | ||
| 334 | If it is not possible or desirable to put the notice in a particular | |
| 335 | file, then You may include the notice in a location (such as a LICENSE | |
| 336 | file in a relevant directory) where a recipient would be likely to look | |
| 337 | for such a notice. | |
| 338 | ||
| 339 | You may add additional accurate notices of copyright ownership. | |
| 340 | ||
| 341 | Exhibit B - “Incompatible With Secondary Licenses” Notice | |
| 342 | ||
| 343 | This Source Code Form is “Incompatible With Secondary Licenses”, as | |
| 344 | defined by the Mozilla Public License, v. 2.0. | |
| 1 | 345 |
| 1 | Copyright (c) 2014, TomasMikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without modification, | |
| 5 | are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright notice, this | |
| 8 | list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright notice, this | |
| 11 | list of conditions and the following disclaimer in the documentation and/or | |
| 12 | other materials provided with the distribution. | |
| 1 | 13 | |
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | |
| 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
| 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 1 | Copyright (c) 2014, TomasMikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright notice, this | |
| 8 | list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | this list of conditions and the following disclaimer in the documentation | |
| 12 | and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 | ||
| 1 | 25 |
| 1 | Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode) | |
| 2 | ||
| 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | This license is copied below, and is also available with a FAQ at: | |
| 5 | http://scripts.sil.org/OFL | |
| 6 | ||
| 7 | ||
| 8 | ----------------------------------------------------------- | |
| 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 10 | ----------------------------------------------------------- | |
| 11 | ||
| 12 | PREAMBLE | |
| 13 | The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 14 | development of collaborative font projects, to support the font creation | |
| 15 | efforts of academic and linguistic communities, and to provide a free and | |
| 16 | open framework in which fonts may be shared and improved in partnership | |
| 17 | with others. | |
| 18 | ||
| 19 | The OFL allows the licensed fonts to be used, studied, modified and | |
| 20 | redistributed freely as long as they are not sold by themselves. The | |
| 21 | fonts, including any derivative works, can be bundled, embedded, | |
| 22 | redistributed and/or sold with any software provided that any reserved | |
| 23 | names are not used by derivative works. The fonts and derivatives, | |
| 24 | however, cannot be released under any other type of license. The | |
| 25 | requirement for fonts to remain under this license does not apply | |
| 26 | to any document created using the fonts or their derivatives. | |
| 27 | ||
| 28 | DEFINITIONS | |
| 29 | "Font Software" refers to the set of files released by the Copyright | |
| 30 | Holder(s) under this license and clearly marked as such. This may | |
| 31 | include source files, build scripts and documentation. | |
| 32 | ||
| 33 | "Reserved Font Name" refers to any names specified as such after the | |
| 34 | copyright statement(s). | |
| 35 | ||
| 36 | "Original Version" refers to the collection of Font Software components as | |
| 37 | distributed by the Copyright Holder(s). | |
| 38 | ||
| 39 | "Modified Version" refers to any derivative made by adding to, deleting, | |
| 40 | or substituting -- in part or in whole -- any of the components of the | |
| 41 | Original Version, by changing formats or by porting the Font Software to a | |
| 42 | new environment. | |
| 43 | ||
| 44 | "Author" refers to any designer, engineer, programmer, technical | |
| 45 | writer or other person who contributed to the Font Software. | |
| 46 | ||
| 47 | PERMISSION & CONDITIONS | |
| 48 | Permission is hereby granted, free of charge, to any person obtaining | |
| 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 50 | redistribute, and sell modified and unmodified copies of the Font | |
| 51 | Software, subject to the following conditions: | |
| 52 | ||
| 53 | 1) Neither the Font Software nor any of its individual components, | |
| 54 | in Original or Modified Versions, may be sold by itself. | |
| 55 | ||
| 56 | 2) Original or Modified Versions of the Font Software may be bundled, | |
| 57 | redistributed and/or sold with any software, provided that each copy | |
| 58 | contains the above copyright notice and this license. These can be | |
| 59 | included either as stand-alone text files, human-readable headers or | |
| 60 | in the appropriate machine-readable metadata fields within text or | |
| 61 | binary files as long as those fields can be easily viewed by the user. | |
| 62 | ||
| 63 | 3) No Modified Version of the Font Software may use the Reserved Font | |
| 64 | Name(s) unless explicit written permission is granted by the corresponding | |
| 65 | Copyright Holder. This restriction only applies to the primary font name as | |
| 66 | presented to the users. | |
| 67 | ||
| 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 69 | Software shall not be used to promote, endorse or advertise any | |
| 70 | Modified Version, except to acknowledge the contribution(s) of the | |
| 71 | Copyright Holder(s) and the Author(s) or with their explicit written | |
| 72 | permission. | |
| 73 | ||
| 74 | 5) The Font Software, modified or unmodified, in part or in whole, | |
| 75 | must be distributed entirely under this license, and must not be | |
| 76 | distributed under any other license. The requirement for fonts to | |
| 77 | remain under this license does not apply to any document created | |
| 78 | using the Font Software. | |
| 79 | ||
| 80 | TERMINATION | |
| 81 | This license becomes null and void if any of the above conditions are | |
| 82 | not met. | |
| 83 | ||
| 84 | DISCLAIMER | |
| 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 93 | OTHER DEALINGS IN THE FONT SOFTWARE. | |
| 1 | 94 |
| 1 | Copyright 2017 The Vollkorn Project Authors (https://github.com/FAlthausen/Vollkorn-Typeface) | |
| 2 | ||
| 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | This license is copied below, and is also available with a FAQ at: | |
| 5 | http://scripts.sil.org/OFL | |
| 6 | ||
| 7 | ||
| 8 | ----------------------------------------------------------- | |
| 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 10 | ----------------------------------------------------------- | |
| 11 | ||
| 12 | PREAMBLE | |
| 13 | The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 14 | development of collaborative font projects, to support the font creation | |
| 15 | efforts of academic and linguistic communities, and to provide a free and | |
| 16 | open framework in which fonts may be shared and improved in partnership | |
| 17 | with others. | |
| 18 | ||
| 19 | The OFL allows the licensed fonts to be used, studied, modified and | |
| 20 | redistributed freely as long as they are not sold by themselves. The | |
| 21 | fonts, including any derivative works, can be bundled, embedded, | |
| 22 | redistributed and/or sold with any software provided that any reserved | |
| 23 | names are not used by derivative works. The fonts and derivatives, | |
| 24 | however, cannot be released under any other type of license. The | |
| 25 | requirement for fonts to remain under this license does not apply | |
| 26 | to any document created using the fonts or their derivatives. | |
| 27 | ||
| 28 | DEFINITIONS | |
| 29 | "Font Software" refers to the set of files released by the Copyright | |
| 30 | Holder(s) under this license and clearly marked as such. This may | |
| 31 | include source files, build scripts and documentation. | |
| 32 | ||
| 33 | "Reserved Font Name" refers to any names specified as such after the | |
| 34 | copyright statement(s). | |
| 35 | ||
| 36 | "Original Version" refers to the collection of Font Software components as | |
| 37 | distributed by the Copyright Holder(s). | |
| 38 | ||
| 39 | "Modified Version" refers to any derivative made by adding to, deleting, | |
| 40 | or substituting -- in part or in whole -- any of the components of the | |
| 41 | Original Version, by changing formats or by porting the Font Software to a | |
| 42 | new environment. | |
| 43 | ||
| 44 | "Author" refers to any designer, engineer, programmer, technical | |
| 45 | writer or other person who contributed to the Font Software. | |
| 46 | ||
| 47 | PERMISSION & CONDITIONS | |
| 48 | Permission is hereby granted, free of charge, to any person obtaining | |
| 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 50 | redistribute, and sell modified and unmodified copies of the Font | |
| 51 | Software, subject to the following conditions: | |
| 52 | ||
| 53 | 1) Neither the Font Software nor any of its individual components, | |
| 54 | in Original or Modified Versions, may be sold by itself. | |
| 55 | ||
| 56 | 2) Original or Modified Versions of the Font Software may be bundled, | |
| 57 | redistributed and/or sold with any software, provided that each copy | |
| 58 | contains the above copyright notice and this license. These can be | |
| 59 | included either as stand-alone text files, human-readable headers or | |
| 60 | in the appropriate machine-readable metadata fields within text or | |
| 61 | binary files as long as those fields can be easily viewed by the user. | |
| 62 | ||
| 63 | 3) No Modified Version of the Font Software may use the Reserved Font | |
| 64 | Name(s) unless explicit written permission is granted by the corresponding | |
| 65 | Copyright Holder. This restriction only applies to the primary font name as | |
| 66 | presented to the users. | |
| 67 | ||
| 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 69 | Software shall not be used to promote, endorse or advertise any | |
| 70 | Modified Version, except to acknowledge the contribution(s) of the | |
| 71 | Copyright Holder(s) and the Author(s) or with their explicit written | |
| 72 | permission. | |
| 73 | ||
| 74 | 5) The Font Software, modified or unmodified, in part or in whole, | |
| 75 | must be distributed entirely under this license, and must not be | |
| 76 | distributed under any other license. The requirement for fonts to | |
| 77 | remain under this license does not apply to any document created | |
| 78 | using the Font Software. | |
| 79 | ||
| 80 | TERMINATION | |
| 81 | This license becomes null and void if any of the above conditions are | |
| 82 | not met. | |
| 83 | ||
| 84 | DISCLAIMER | |
| 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 93 | OTHER DEALINGS IN THE FONT SOFTWARE. | |
| 1 | 94 |
| 1 | 1 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Settings; | |
| 31 | import com.scrivenvar.util.ProtocolScheme; | |
| 32 | ||
| 33 | import java.nio.file.Path; | |
| 34 | ||
| 35 | import static com.scrivenvar.Constants.GLOB_PREFIX_FILE; | |
| 36 | import static com.scrivenvar.Constants.SETTINGS; | |
| 37 | import static com.scrivenvar.FileType.UNKNOWN; | |
| 38 | import static com.scrivenvar.predicates.PredicateFactory.createFileTypePredicate; | |
| 39 | import static java.lang.String.format; | |
| 40 | ||
| 41 | /** | |
| 42 | * Provides common behaviours for factories that instantiate classes based on | |
| 43 | * file type. | |
| 44 | */ | |
| 45 | public class AbstractFileFactory { | |
| 46 | ||
| 47 | private static final String MSG_UNKNOWN_FILE_TYPE = | |
| 48 | "Unknown type '%s' for file '%s'."; | |
| 49 | ||
| 50 | /** | |
| 51 | * Determines the file type from the path extension. This should only be | |
| 52 | * called when it is known that the file type won't be a definition file | |
| 53 | * (e.g., YAML or other definition source), but rather an editable file | |
| 54 | * (e.g., Markdown, XML, etc.). | |
| 55 | * | |
| 56 | * @param path The path with a file name extension. | |
| 57 | * @return The FileType for the given path. | |
| 58 | */ | |
| 59 | public FileType lookup( final Path path ) { | |
| 60 | return lookup( path, GLOB_PREFIX_FILE ); | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Creates a file type that corresponds to the given path. | |
| 65 | * | |
| 66 | * @param path Reference to a variable definition file. | |
| 67 | * @param prefix One of GLOB_PREFIX_DEFINITION or GLOB_PREFIX_FILE. | |
| 68 | * @return The file type that corresponds to the given path. | |
| 69 | */ | |
| 70 | protected FileType lookup( final Path path, final String prefix ) { | |
| 71 | assert path != null; | |
| 72 | assert prefix != null; | |
| 73 | ||
| 74 | final var settings = getSettings(); | |
| 75 | final var keys = settings.getKeys( prefix ); | |
| 76 | ||
| 77 | var found = false; | |
| 78 | var fileType = UNKNOWN; | |
| 79 | ||
| 80 | while( keys.hasNext() && !found ) { | |
| 81 | final var key = keys.next(); | |
| 82 | final var patterns = settings.getStringSettingList( key ); | |
| 83 | final var predicate = createFileTypePredicate( patterns ); | |
| 84 | ||
| 85 | if( found = predicate.test( path.toFile() ) ) { | |
| 86 | // Remove the EXTENSIONS_PREFIX to get the filename extension mapped | |
| 87 | // to a standard name (as defined in the settings.properties file). | |
| 88 | final String suffix = key.replace( prefix + ".", "" ); | |
| 89 | fileType = FileType.from( suffix ); | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | return fileType; | |
| 94 | } | |
| 95 | ||
| 96 | /** | |
| 97 | * Throws IllegalArgumentException because the given path could not be | |
| 98 | * recognized. This exists because | |
| 99 | * | |
| 100 | * @param type The detected path type (protocol, file extension, etc.). | |
| 101 | * @param path The path to a source of definitions. | |
| 102 | */ | |
| 103 | protected void unknownFileType( | |
| 104 | final ProtocolScheme type, final String path ) { | |
| 105 | final String msg = format( MSG_UNKNOWN_FILE_TYPE, type, path ); | |
| 106 | throw new IllegalArgumentException( msg ); | |
| 107 | } | |
| 108 | ||
| 109 | /** | |
| 110 | * Return the singleton Settings instance. | |
| 111 | * | |
| 112 | * @return A non-null instance. | |
| 113 | */ | |
| 114 | private Settings getSettings() { | |
| 115 | return SETTINGS; | |
| 116 | } | |
| 117 | } | |
| 1 | 118 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Settings; | |
| 31 | ||
| 32 | import java.nio.file.Path; | |
| 33 | import java.nio.file.Paths; | |
| 34 | ||
| 35 | /** | |
| 36 | * Defines application-wide default values. | |
| 37 | */ | |
| 38 | public class Constants { | |
| 39 | ||
| 40 | public static final Settings SETTINGS = Services.load( Settings.class ); | |
| 41 | ||
| 42 | /** | |
| 43 | * Prevent instantiation. | |
| 44 | */ | |
| 45 | private Constants() { | |
| 46 | } | |
| 47 | ||
| 48 | private static String get( final String key ) { | |
| 49 | return SETTINGS.getSetting( key, "" ); | |
| 50 | } | |
| 51 | ||
| 52 | @SuppressWarnings("SameParameterValue") | |
| 53 | private static int get( final String key, final int defaultValue ) { | |
| 54 | return SETTINGS.getSetting( key, defaultValue ); | |
| 55 | } | |
| 56 | ||
| 57 | // Bootstrapping... | |
| 58 | public static final String SETTINGS_NAME = | |
| 59 | "/com/scrivenvar/settings.properties"; | |
| 60 | ||
| 61 | public static final String DEFINITION_NAME = "variables.yaml"; | |
| 62 | ||
| 63 | public static final String APP_TITLE = get( "application.title" ); | |
| 64 | public static final String APP_BUNDLE_NAME = get( "application.messages" ); | |
| 65 | ||
| 66 | // Prevent double events when updating files on Linux (save and timestamp). | |
| 67 | public static final int APP_WATCHDOG_TIMEOUT = get( | |
| 68 | "application.watchdog.timeout", 200 ); | |
| 69 | ||
| 70 | public static final String STYLESHEET_SCENE = get( "file.stylesheet.scene" ); | |
| 71 | public static final String STYLESHEET_MARKDOWN = get( | |
| 72 | "file.stylesheet.markdown" ); | |
| 73 | public static final String STYLESHEET_PREVIEW = get( | |
| 74 | "file.stylesheet.preview" ); | |
| 75 | ||
| 76 | public static final String FILE_LOGO_16 = get( "file.logo.16" ); | |
| 77 | public static final String FILE_LOGO_32 = get( "file.logo.32" ); | |
| 78 | public static final String FILE_LOGO_128 = get( "file.logo.128" ); | |
| 79 | public static final String FILE_LOGO_256 = get( "file.logo.256" ); | |
| 80 | public static final String FILE_LOGO_512 = get( "file.logo.512" ); | |
| 81 | ||
| 82 | public static final String PREFS_ROOT = get( "preferences.root" ); | |
| 83 | public static final String PREFS_STATE = get( "preferences.root.state" ); | |
| 84 | ||
| 85 | /** | |
| 86 | * Refer to filename extension settings in the configuration file. Do not | |
| 87 | * terminate these prefixes with a period. | |
| 88 | */ | |
| 89 | public static final String GLOB_PREFIX_FILE = "file.ext"; | |
| 90 | public static final String GLOB_PREFIX_DEFINITION = | |
| 91 | "definition." + GLOB_PREFIX_FILE; | |
| 92 | ||
| 93 | /** | |
| 94 | * Three parameters: line number, column number, and offset. | |
| 95 | */ | |
| 96 | public static final String STATUS_BAR_LINE = "Main.status.line"; | |
| 97 | ||
| 98 | public static final String STATUS_BAR_OK = "Main.status.state.default"; | |
| 99 | ||
| 100 | /** | |
| 101 | * Used to show an error while parsing, usually syntactical. | |
| 102 | */ | |
| 103 | public static final String STATUS_PARSE_ERROR = "Main.status.error.parse"; | |
| 104 | public static final String STATUS_DEFINITION_BLANK = "Main.status.error.def.blank"; | |
| 105 | public static final String STATUS_DEFINITION_EMPTY = "Main.status.error.def.empty"; | |
| 106 | ||
| 107 | /** | |
| 108 | * One parameter: the word under the cursor that could not be found. | |
| 109 | */ | |
| 110 | public static final String STATUS_DEFINITION_MISSING = "Main.status.error.def.missing"; | |
| 111 | ||
| 112 | /** | |
| 113 | * Used when creating flat maps relating to resolved variables. | |
| 114 | */ | |
| 115 | public static final int DEFAULT_MAP_SIZE = 64; | |
| 116 | ||
| 117 | /** | |
| 118 | * Default image extension order to use when scanning. | |
| 119 | */ | |
| 120 | public static final String PERSIST_IMAGES_DEFAULT = | |
| 121 | get( "file.ext.image.order" ); | |
| 122 | ||
| 123 | /** | |
| 124 | * Default working directory to use for R startup script. | |
| 125 | */ | |
| 126 | public static final String USER_DIRECTORY = System.getProperty( "user.dir" ); | |
| 127 | ||
| 128 | /** | |
| 129 | * Default path to use for an untitled (pathless) file. | |
| 130 | */ | |
| 131 | public static final Path DEFAULT_DIRECTORY = Paths.get( USER_DIRECTORY ); | |
| 132 | ||
| 133 | /** | |
| 134 | * Default starting delimiter for definition variables. | |
| 135 | */ | |
| 136 | public static final String DEF_DELIM_BEGAN_DEFAULT = "${"; | |
| 137 | ||
| 138 | /** | |
| 139 | * Default ending delimiter for definition variables. | |
| 140 | */ | |
| 141 | public static final String DEF_DELIM_ENDED_DEFAULT = "}"; | |
| 142 | ||
| 143 | /** | |
| 144 | * Default starting delimiter when inserting R variables. | |
| 145 | */ | |
| 146 | public static final String R_DELIM_BEGAN_DEFAULT = "x( "; | |
| 147 | ||
| 148 | /** | |
| 149 | * Default ending delimiter when inserting R variables. | |
| 150 | */ | |
| 151 | public static final String R_DELIM_ENDED_DEFAULT = " )"; | |
| 152 | ||
| 153 | /** | |
| 154 | * Resource directory where different language lexicons are located. | |
| 155 | */ | |
| 156 | public static final String LEXICONS_DIRECTORY = "lexicons"; | |
| 157 | ||
| 158 | /** | |
| 159 | * Used as the prefix for uniquely identifying HTML block elements, which | |
| 160 | * helps coordinate scrolling the preview pane to where the user is typing. | |
| 161 | */ | |
| 162 | public static final String PARAGRAPH_ID_PREFIX = "p-"; | |
| 163 | ||
| 164 | /** | |
| 165 | * Absolute location of true type font files within the Java archive file. | |
| 166 | */ | |
| 167 | public static final String FONT_DIRECTORY = "/fonts"; | |
| 168 | ||
| 169 | /** | |
| 170 | * Default text editor font size, in points. | |
| 171 | */ | |
| 172 | public static final int FONT_SIZE_EDITOR = 12; | |
| 173 | } | |
| 1 | 174 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * Redistribution and use in source and binary forms, with or without | |
| 5 | * modification, are permitted provided that the following conditions are met: | |
| 6 | * | |
| 7 | * o Redistributions of source code must retain the above copyright | |
| 8 | * notice, this list of conditions and the following disclaimer. | |
| 9 | * | |
| 10 | * o Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * | |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 18 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 19 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 20 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 | */ | |
| 26 | package com.scrivenvar; | |
| 27 | ||
| 28 | import com.scrivenvar.editors.EditorPane; | |
| 29 | import com.scrivenvar.editors.markdown.MarkdownEditorPane; | |
| 30 | import com.scrivenvar.service.events.Notification; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 32 | import javafx.beans.binding.Bindings; | |
| 33 | import javafx.beans.property.BooleanProperty; | |
| 34 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 35 | import javafx.beans.property.ReadOnlyBooleanWrapper; | |
| 36 | import javafx.beans.property.SimpleBooleanProperty; | |
| 37 | import javafx.beans.value.ChangeListener; | |
| 38 | import javafx.event.Event; | |
| 39 | import javafx.event.EventHandler; | |
| 40 | import javafx.event.EventType; | |
| 41 | import javafx.scene.Scene; | |
| 42 | import javafx.scene.control.Tab; | |
| 43 | import javafx.scene.control.Tooltip; | |
| 44 | import javafx.scene.text.Text; | |
| 45 | import javafx.stage.Window; | |
| 46 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 47 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 48 | import org.fxmisc.undo.UndoManager; | |
| 49 | import org.jetbrains.annotations.NotNull; | |
| 50 | import org.mozilla.universalchardet.UniversalDetector; | |
| 51 | ||
| 52 | import java.io.File; | |
| 53 | import java.nio.charset.Charset; | |
| 54 | import java.nio.file.Files; | |
| 55 | import java.nio.file.Path; | |
| 56 | ||
| 57 | import static com.scrivenvar.Messages.get; | |
| 58 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 59 | import static com.scrivenvar.StatusBarNotifier.getNotifier; | |
| 60 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 61 | import static java.util.Locale.ENGLISH; | |
| 62 | import static javafx.application.Platform.runLater; | |
| 63 | ||
| 64 | /** | |
| 65 | * Editor for a single file. | |
| 66 | */ | |
| 67 | public final class FileEditorTab extends Tab { | |
| 68 | ||
| 69 | private final MarkdownEditorPane mEditorPane = new MarkdownEditorPane(); | |
| 70 | ||
| 71 | private final ReadOnlyBooleanWrapper mModified = new ReadOnlyBooleanWrapper(); | |
| 72 | private final BooleanProperty canUndo = new SimpleBooleanProperty(); | |
| 73 | private final BooleanProperty canRedo = new SimpleBooleanProperty(); | |
| 74 | ||
| 75 | /** | |
| 76 | * Character encoding used by the file (or default encoding if none found). | |
| 77 | */ | |
| 78 | private Charset mEncoding = UTF_8; | |
| 79 | ||
| 80 | /** | |
| 81 | * File to load into the editor. | |
| 82 | */ | |
| 83 | private Path mPath; | |
| 84 | ||
| 85 | public FileEditorTab( final Path path ) { | |
| 86 | setPath( path ); | |
| 87 | ||
| 88 | mModified.addListener( ( observable, oldPath, newPath ) -> updateTab() ); | |
| 89 | ||
| 90 | setOnSelectionChanged( e -> { | |
| 91 | if( isSelected() ) { | |
| 92 | runLater( this::activated ); | |
| 93 | requestFocus(); | |
| 94 | } | |
| 95 | } ); | |
| 96 | } | |
| 97 | ||
| 98 | private void updateTab() { | |
| 99 | setText( getTabTitle() ); | |
| 100 | setGraphic( getModifiedMark() ); | |
| 101 | setTooltip( getTabTooltip() ); | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * Returns the base filename (without the directory names). | |
| 106 | * | |
| 107 | * @return The untitled text if the path hasn't been set. | |
| 108 | */ | |
| 109 | private String getTabTitle() { | |
| 110 | return getPath().getFileName().toString(); | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Returns the full filename represented by the path. | |
| 115 | * | |
| 116 | * @return The untitled text if the path hasn't been set. | |
| 117 | */ | |
| 118 | private Tooltip getTabTooltip() { | |
| 119 | final Path filePath = getPath(); | |
| 120 | return new Tooltip( filePath == null ? "" : filePath.toString() ); | |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * Returns a marker to indicate whether the file has been modified. | |
| 125 | * | |
| 126 | * @return "*" when the file has changed; otherwise null. | |
| 127 | */ | |
| 128 | private Text getModifiedMark() { | |
| 129 | return isModified() ? new Text( "*" ) : null; | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Called when the user switches tab. | |
| 134 | */ | |
| 135 | private void activated() { | |
| 136 | // Tab is closed or no longer active. | |
| 137 | if( getTabPane() == null || !isSelected() ) { | |
| 138 | return; | |
| 139 | } | |
| 140 | ||
| 141 | // If the tab is devoid of content, load it. | |
| 142 | if( getContent() == null ) { | |
| 143 | readFile(); | |
| 144 | initLayout(); | |
| 145 | initUndoManager(); | |
| 146 | } | |
| 147 | } | |
| 148 | ||
| 149 | private void initLayout() { | |
| 150 | setContent( getScrollPane() ); | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Tracks undo requests, but can only be called <em>after</em> load. | |
| 155 | */ | |
| 156 | private void initUndoManager() { | |
| 157 | final UndoManager<?> undoManager = getUndoManager(); | |
| 158 | undoManager.forgetHistory(); | |
| 159 | ||
| 160 | // Bind the editor undo manager to the properties. | |
| 161 | mModified.bind( Bindings.not( undoManager.atMarkedPositionProperty() ) ); | |
| 162 | canUndo.bind( undoManager.undoAvailableProperty() ); | |
| 163 | canRedo.bind( undoManager.redoAvailableProperty() ); | |
| 164 | } | |
| 165 | ||
| 166 | private void requestFocus() { | |
| 167 | getEditorPane().requestFocus(); | |
| 168 | } | |
| 169 | ||
| 170 | /** | |
| 171 | * Searches from the caret position forward for the given string. | |
| 172 | * | |
| 173 | * @param needle The text string to match. | |
| 174 | */ | |
| 175 | public void searchNext( final String needle ) { | |
| 176 | final String haystack = getEditorText(); | |
| 177 | int index = haystack.indexOf( needle, getCaretPosition() ); | |
| 178 | ||
| 179 | // Wrap around. | |
| 180 | if( index == -1 ) { | |
| 181 | index = haystack.indexOf( needle ); | |
| 182 | } | |
| 183 | ||
| 184 | if( index >= 0 ) { | |
| 185 | setCaretPosition( index ); | |
| 186 | getEditor().selectRange( index, index + needle.length() ); | |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Gets a reference to the scroll pane that houses the editor. | |
| 192 | * | |
| 193 | * @return The editor's scroll pane, containing a vertical scrollbar. | |
| 194 | */ | |
| 195 | public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() { | |
| 196 | return getEditorPane().getScrollPane(); | |
| 197 | } | |
| 198 | ||
| 199 | /** | |
| 200 | * Returns the index into the text where the caret blinks happily away. | |
| 201 | * | |
| 202 | * @return A number from 0 to the editor's document text length. | |
| 203 | */ | |
| 204 | public int getCaretPosition() { | |
| 205 | return getEditor().getCaretPosition(); | |
| 206 | } | |
| 207 | ||
| 208 | /** | |
| 209 | * Moves the caret to a given offset. | |
| 210 | * | |
| 211 | * @param offset The new caret offset. | |
| 212 | */ | |
| 213 | private void setCaretPosition( final int offset ) { | |
| 214 | getEditor().moveTo( offset ); | |
| 215 | getEditor().requestFollowCaret(); | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * Returns the text area associated with this tab. | |
| 220 | * | |
| 221 | * @return A text editor. | |
| 222 | */ | |
| 223 | private StyleClassedTextArea getEditor() { | |
| 224 | return getEditorPane().getEditor(); | |
| 225 | } | |
| 226 | ||
| 227 | /** | |
| 228 | * Returns true if the given path exactly matches this tab's path. | |
| 229 | * | |
| 230 | * @param check The path to compare against. | |
| 231 | * @return true The paths are the same. | |
| 232 | */ | |
| 233 | public boolean isPath( final Path check ) { | |
| 234 | final Path filePath = getPath(); | |
| 235 | ||
| 236 | return filePath != null && filePath.equals( check ); | |
| 237 | } | |
| 238 | ||
| 239 | /** | |
| 240 | * Reads the entire file contents from the path associated with this tab. | |
| 241 | */ | |
| 242 | private void readFile() { | |
| 243 | final Path path = getPath(); | |
| 244 | final File file = path.toFile(); | |
| 245 | ||
| 246 | try { | |
| 247 | if( file.exists() ) { | |
| 248 | if( file.canWrite() && file.canRead() ) { | |
| 249 | final EditorPane pane = getEditorPane(); | |
| 250 | pane.setText( asString( Files.readAllBytes( path ) ) ); | |
| 251 | pane.scrollToTop(); | |
| 252 | } | |
| 253 | else { | |
| 254 | final String msg = get( "FileEditor.loadFailed.reason.permissions" ); | |
| 255 | alert( "FileEditor.loadFailed.message", file.toString(), msg ); | |
| 256 | } | |
| 257 | } | |
| 258 | } catch( final Exception ex ) { | |
| 259 | alert( ex ); | |
| 260 | } | |
| 261 | } | |
| 262 | ||
| 263 | /** | |
| 264 | * Saves the entire file contents from the path associated with this tab. | |
| 265 | * | |
| 266 | * @return true The file has been saved. | |
| 267 | */ | |
| 268 | public boolean save() { | |
| 269 | try { | |
| 270 | final EditorPane editor = getEditorPane(); | |
| 271 | Files.write( getPath(), asBytes( editor.getText() ) ); | |
| 272 | editor.getUndoManager().mark(); | |
| 273 | return true; | |
| 274 | } catch( final Exception ex ) { | |
| 275 | return popupAlert( | |
| 276 | "FileEditor.saveFailed.title", | |
| 277 | "FileEditor.saveFailed.message", | |
| 278 | ex | |
| 279 | ); | |
| 280 | } | |
| 281 | } | |
| 282 | ||
| 283 | /** | |
| 284 | * Creates an alert dialog and waits for it to close. | |
| 285 | * | |
| 286 | * @param titleKey Resource bundle key for the alert dialog title. | |
| 287 | * @param messageKey Resource bundle key for the alert dialog message. | |
| 288 | * @param e The unexpected happening. | |
| 289 | * @return false | |
| 290 | */ | |
| 291 | @SuppressWarnings("SameParameterValue") | |
| 292 | private boolean popupAlert( | |
| 293 | final String titleKey, final String messageKey, final Exception e ) { | |
| 294 | final Notifier service = getNotifier(); | |
| 295 | final Path filePath = getPath(); | |
| 296 | ||
| 297 | final Notification message = service.createNotification( | |
| 298 | get( titleKey ), | |
| 299 | get( messageKey ), | |
| 300 | filePath == null ? "" : filePath, | |
| 301 | e.getMessage() | |
| 302 | ); | |
| 303 | ||
| 304 | try { | |
| 305 | service.createError( getWindow(), message ).showAndWait(); | |
| 306 | } catch( final Exception ex ) { | |
| 307 | alert( ex ); | |
| 308 | } | |
| 309 | ||
| 310 | return false; | |
| 311 | } | |
| 312 | ||
| 313 | private Window getWindow() { | |
| 314 | final Scene scene = getEditorPane().getScene(); | |
| 315 | ||
| 316 | if( scene == null ) { | |
| 317 | throw new UnsupportedOperationException( "No scene window available" ); | |
| 318 | } | |
| 319 | ||
| 320 | return scene.getWindow(); | |
| 321 | } | |
| 322 | ||
| 323 | /** | |
| 324 | * Returns a best guess at the file encoding. If the encoding could not be | |
| 325 | * detected, this will return the default charset for the JVM. | |
| 326 | * | |
| 327 | * @param bytes The bytes to perform character encoding detection. | |
| 328 | * @return The character encoding. | |
| 329 | */ | |
| 330 | private Charset detectEncoding( final byte[] bytes ) { | |
| 331 | final var detector = new UniversalDetector( null ); | |
| 332 | detector.handleData( bytes, 0, bytes.length ); | |
| 333 | detector.dataEnd(); | |
| 334 | ||
| 335 | final String charset = detector.getDetectedCharset(); | |
| 336 | ||
| 337 | return charset == null | |
| 338 | ? Charset.defaultCharset() | |
| 339 | : Charset.forName( charset.toUpperCase( ENGLISH ) ); | |
| 340 | } | |
| 341 | ||
| 342 | /** | |
| 343 | * Converts the given string to an array of bytes using the encoding that was | |
| 344 | * originally detected (if any) and associated with this file. | |
| 345 | * | |
| 346 | * @param text The text to convert into the original file encoding. | |
| 347 | * @return A series of bytes ready for writing to a file. | |
| 348 | */ | |
| 349 | private byte[] asBytes( final String text ) { | |
| 350 | return text.getBytes( getEncoding() ); | |
| 351 | } | |
| 352 | ||
| 353 | /** | |
| 354 | * Converts the given bytes into a Java String. This will call setEncoding | |
| 355 | * with the encoding detected by the CharsetDetector. | |
| 356 | * | |
| 357 | * @param text The text of unknown character encoding. | |
| 358 | * @return The text, in its auto-detected encoding, as a String. | |
| 359 | */ | |
| 360 | private String asString( final byte[] text ) { | |
| 361 | setEncoding( detectEncoding( text ) ); | |
| 362 | return new String( text, getEncoding() ); | |
| 363 | } | |
| 364 | ||
| 365 | /** | |
| 366 | * Returns the path to the file being edited in this tab. | |
| 367 | * | |
| 368 | * @return A non-null instance. | |
| 369 | */ | |
| 370 | public Path getPath() { | |
| 371 | return mPath; | |
| 372 | } | |
| 373 | ||
| 374 | /** | |
| 375 | * Sets the path to a file for editing and then updates the tab with the | |
| 376 | * file contents. | |
| 377 | * | |
| 378 | * @param path A non-null instance. | |
| 379 | */ | |
| 380 | public void setPath( final Path path ) { | |
| 381 | assert path != null; | |
| 382 | mPath = path; | |
| 383 | ||
| 384 | updateTab(); | |
| 385 | } | |
| 386 | ||
| 387 | public boolean isModified() { | |
| 388 | return mModified.get(); | |
| 389 | } | |
| 390 | ||
| 391 | ReadOnlyBooleanProperty modifiedProperty() { | |
| 392 | return mModified.getReadOnlyProperty(); | |
| 393 | } | |
| 394 | ||
| 395 | BooleanProperty canUndoProperty() { | |
| 396 | return this.canUndo; | |
| 397 | } | |
| 398 | ||
| 399 | BooleanProperty canRedoProperty() { | |
| 400 | return this.canRedo; | |
| 401 | } | |
| 402 | ||
| 403 | private UndoManager<?> getUndoManager() { | |
| 404 | return getEditorPane().getUndoManager(); | |
| 405 | } | |
| 406 | ||
| 407 | /** | |
| 408 | * Forwards to the editor pane's listeners for text change events. | |
| 409 | * | |
| 410 | * @param listener The listener to notify when the text changes. | |
| 411 | */ | |
| 412 | public void addTextChangeListener( final ChangeListener<String> listener ) { | |
| 413 | getEditorPane().addTextChangeListener( listener ); | |
| 414 | } | |
| 415 | ||
| 416 | /** | |
| 417 | * Forwards to the editor pane's listeners for caret change events. | |
| 418 | * | |
| 419 | * @param listener Notified when the caret position changes. | |
| 420 | */ | |
| 421 | public void addCaretPositionListener( | |
| 422 | final ChangeListener<? super Integer> listener ) { | |
| 423 | getEditorPane().addCaretPositionListener( listener ); | |
| 424 | } | |
| 425 | ||
| 426 | /** | |
| 427 | * Forwards to the editor pane's listeners for paragraph index change events. | |
| 428 | * | |
| 429 | * @param listener Notified when the caret's paragraph index changes. | |
| 430 | */ | |
| 431 | public void addCaretParagraphListener( | |
| 432 | final ChangeListener<? super Integer> listener ) { | |
| 433 | getEditorPane().addCaretParagraphListener( listener ); | |
| 434 | } | |
| 435 | ||
| 436 | public <T extends Event> void addEventFilter( | |
| 437 | final EventType<T> eventType, | |
| 438 | final EventHandler<? super T> eventFilter ) { | |
| 439 | getEditor().addEventFilter( eventType, eventFilter ); | |
| 440 | } | |
| 441 | ||
| 442 | /** | |
| 443 | * Forwards the request to the editor pane. | |
| 444 | * | |
| 445 | * @return The text to process. | |
| 446 | */ | |
| 447 | public String getEditorText() { | |
| 448 | return getEditorPane().getText(); | |
| 449 | } | |
| 450 | ||
| 451 | /** | |
| 452 | * Returns the editor pane, or creates one if it doesn't yet exist. | |
| 453 | * | |
| 454 | * @return The editor pane, never null. | |
| 455 | */ | |
| 456 | @NotNull | |
| 457 | public MarkdownEditorPane getEditorPane() { | |
| 458 | return mEditorPane; | |
| 459 | } | |
| 460 | ||
| 461 | /** | |
| 462 | * Returns the encoding for the file, defaulting to UTF-8 if it hasn't been | |
| 463 | * determined. | |
| 464 | * | |
| 465 | * @return The file encoding or UTF-8 if unknown. | |
| 466 | */ | |
| 467 | private Charset getEncoding() { | |
| 468 | return mEncoding; | |
| 469 | } | |
| 470 | ||
| 471 | private void setEncoding( final Charset encoding ) { | |
| 472 | assert encoding != null; | |
| 473 | mEncoding = encoding; | |
| 474 | } | |
| 475 | ||
| 476 | /** | |
| 477 | * Returns the tab title, without any modified indicators. | |
| 478 | * | |
| 479 | * @return The tab title. | |
| 480 | */ | |
| 481 | @Override | |
| 482 | public String toString() { | |
| 483 | return getTabTitle(); | |
| 484 | } | |
| 485 | } | |
| 1 | 486 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Options; | |
| 31 | import com.scrivenvar.service.Settings; | |
| 32 | import com.scrivenvar.service.events.Notification; | |
| 33 | import com.scrivenvar.service.events.Notifier; | |
| 34 | import com.scrivenvar.util.Utils; | |
| 35 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 36 | import javafx.beans.property.ReadOnlyBooleanWrapper; | |
| 37 | import javafx.beans.property.ReadOnlyObjectProperty; | |
| 38 | import javafx.beans.property.ReadOnlyObjectWrapper; | |
| 39 | import javafx.beans.value.ChangeListener; | |
| 40 | import javafx.collections.ListChangeListener; | |
| 41 | import javafx.collections.ObservableList; | |
| 42 | import javafx.event.Event; | |
| 43 | import javafx.scene.Node; | |
| 44 | import javafx.scene.control.Alert; | |
| 45 | import javafx.scene.control.ButtonType; | |
| 46 | import javafx.scene.control.Tab; | |
| 47 | import javafx.scene.control.TabPane; | |
| 48 | import javafx.stage.FileChooser; | |
| 49 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 50 | import javafx.stage.Window; | |
| 51 | ||
| 52 | import java.io.File; | |
| 53 | import java.nio.file.Path; | |
| 54 | import java.util.ArrayList; | |
| 55 | import java.util.List; | |
| 56 | import java.util.Optional; | |
| 57 | import java.util.concurrent.atomic.AtomicReference; | |
| 58 | import java.util.prefs.Preferences; | |
| 59 | import java.util.stream.Collectors; | |
| 60 | ||
| 61 | import static com.scrivenvar.Constants.GLOB_PREFIX_FILE; | |
| 62 | import static com.scrivenvar.Constants.SETTINGS; | |
| 63 | import static com.scrivenvar.FileType.*; | |
| 64 | import static com.scrivenvar.Messages.get; | |
| 65 | import static com.scrivenvar.predicates.PredicateFactory.createFileTypePredicate; | |
| 66 | import static com.scrivenvar.service.events.Notifier.YES; | |
| 67 | ||
| 68 | /** | |
| 69 | * Tab pane for file editors. | |
| 70 | */ | |
| 71 | public final class FileEditorTabPane extends TabPane { | |
| 72 | ||
| 73 | private static final String FILTER_EXTENSION_TITLES = | |
| 74 | "Dialog.file.choose.filter"; | |
| 75 | ||
| 76 | private static final Options sOptions = Services.load( Options.class ); | |
| 77 | private static final Notifier sNotifier = Services.load( Notifier.class ); | |
| 78 | ||
| 79 | private final ReadOnlyObjectWrapper<Path> mOpenDefinition = | |
| 80 | new ReadOnlyObjectWrapper<>(); | |
| 81 | private final ReadOnlyObjectWrapper<FileEditorTab> mActiveFileEditor = | |
| 82 | new ReadOnlyObjectWrapper<>(); | |
| 83 | private final ReadOnlyBooleanWrapper mAnyFileEditorModified = | |
| 84 | new ReadOnlyBooleanWrapper(); | |
| 85 | private final ChangeListener<Integer> mCaretPositionListener; | |
| 86 | private final ChangeListener<Integer> mCaretParagraphListener; | |
| 87 | ||
| 88 | /** | |
| 89 | * Constructs a new file editor tab pane. | |
| 90 | * | |
| 91 | * @param caretPositionListener Listens for changes to caret position so | |
| 92 | * that the status bar can update. | |
| 93 | * @param caretParagraphListener Listens for changes to the caret's paragraph | |
| 94 | * so that scrolling may occur. | |
| 95 | */ | |
| 96 | public FileEditorTabPane( | |
| 97 | final ChangeListener<Integer> caretPositionListener, | |
| 98 | final ChangeListener<Integer> caretParagraphListener ) { | |
| 99 | final ObservableList<Tab> tabs = getTabs(); | |
| 100 | ||
| 101 | setFocusTraversable( false ); | |
| 102 | setTabClosingPolicy( TabClosingPolicy.ALL_TABS ); | |
| 103 | ||
| 104 | addTabSelectionListener( | |
| 105 | ( tabPane, oldTab, newTab ) -> { | |
| 106 | if( newTab != null ) { | |
| 107 | mActiveFileEditor.set( (FileEditorTab) newTab ); | |
| 108 | } | |
| 109 | } | |
| 110 | ); | |
| 111 | ||
| 112 | final ChangeListener<Boolean> modifiedListener = | |
| 113 | ( observable, oldValue, newValue ) -> { | |
| 114 | for( final Tab tab : tabs ) { | |
| 115 | if( ((FileEditorTab) tab).isModified() ) { | |
| 116 | mAnyFileEditorModified.set( true ); | |
| 117 | break; | |
| 118 | } | |
| 119 | } | |
| 120 | }; | |
| 121 | ||
| 122 | tabs.addListener( | |
| 123 | (ListChangeListener<Tab>) change -> { | |
| 124 | while( change.next() ) { | |
| 125 | if( change.wasAdded() ) { | |
| 126 | change.getAddedSubList().forEach( | |
| 127 | ( tab ) -> { | |
| 128 | final var fet = (FileEditorTab) tab; | |
| 129 | fet.modifiedProperty().addListener( modifiedListener ); | |
| 130 | } ); | |
| 131 | } | |
| 132 | else if( change.wasRemoved() ) { | |
| 133 | change.getRemoved().forEach( | |
| 134 | ( tab ) -> { | |
| 135 | final var fet = (FileEditorTab) tab; | |
| 136 | fet.modifiedProperty().removeListener( modifiedListener ); | |
| 137 | } | |
| 138 | ); | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | // Changes in the tabs may also change anyFileEditorModified property | |
| 143 | // (e.g. closed modified file) | |
| 144 | modifiedListener.changed( null, null, null ); | |
| 145 | } | |
| 146 | ); | |
| 147 | ||
| 148 | mCaretPositionListener = caretPositionListener; | |
| 149 | mCaretParagraphListener = caretParagraphListener; | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Allows observers to be notified when the current file editor tab changes. | |
| 154 | * | |
| 155 | * @param listener The listener to notify of tab change events. | |
| 156 | */ | |
| 157 | public void addTabSelectionListener( final ChangeListener<Tab> listener ) { | |
| 158 | // Observe the tab so that when a new tab is opened or selected, | |
| 159 | // a notification is kicked off. | |
| 160 | getSelectionModel().selectedItemProperty().addListener( listener ); | |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Returns the tab that has keyboard focus. | |
| 165 | * | |
| 166 | * @return A non-null instance. | |
| 167 | */ | |
| 168 | public FileEditorTab getActiveFileEditor() { | |
| 169 | return mActiveFileEditor.get(); | |
| 170 | } | |
| 171 | ||
| 172 | /** | |
| 173 | * Returns the property corresponding to the tab that has focus. | |
| 174 | * | |
| 175 | * @return A non-null instance. | |
| 176 | */ | |
| 177 | public ReadOnlyObjectProperty<FileEditorTab> activeFileEditorProperty() { | |
| 178 | return mActiveFileEditor.getReadOnlyProperty(); | |
| 179 | } | |
| 180 | ||
| 181 | /** | |
| 182 | * Property that can answer whether the text has been modified. | |
| 183 | * | |
| 184 | * @return A non-null instance, true meaning the content has not been saved. | |
| 185 | */ | |
| 186 | ReadOnlyBooleanProperty anyFileEditorModifiedProperty() { | |
| 187 | return mAnyFileEditorModified.getReadOnlyProperty(); | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Creates a new editor instance from the given path. | |
| 192 | * | |
| 193 | * @param path The file to open. | |
| 194 | * @return A non-null instance. | |
| 195 | */ | |
| 196 | private FileEditorTab createFileEditor( final Path path ) { | |
| 197 | assert path != null; | |
| 198 | ||
| 199 | final FileEditorTab tab = new FileEditorTab( path ); | |
| 200 | ||
| 201 | tab.setOnCloseRequest( e -> { | |
| 202 | if( !canCloseEditor( tab ) ) { | |
| 203 | e.consume(); | |
| 204 | } | |
| 205 | else if( isActiveFileEditor( tab ) ) { | |
| 206 | // Prevent prompting the user to save when there are no file editor | |
| 207 | // tabs open. | |
| 208 | mActiveFileEditor.set( null ); | |
| 209 | } | |
| 210 | } ); | |
| 211 | ||
| 212 | tab.addCaretPositionListener( mCaretPositionListener ); | |
| 213 | tab.addCaretParagraphListener( mCaretParagraphListener ); | |
| 214 | ||
| 215 | return tab; | |
| 216 | } | |
| 217 | ||
| 218 | private boolean isActiveFileEditor( final FileEditorTab tab ) { | |
| 219 | return getActiveFileEditor() == tab; | |
| 220 | } | |
| 221 | ||
| 222 | private Path getDefaultPath() { | |
| 223 | final String filename = getDefaultFilename(); | |
| 224 | return (new File( filename )).toPath(); | |
| 225 | } | |
| 226 | ||
| 227 | private String getDefaultFilename() { | |
| 228 | return getSettings().getSetting( "file.default", "untitled.md" ); | |
| 229 | } | |
| 230 | ||
| 231 | /** | |
| 232 | * Called to add a new {@link FileEditorTab} to the tab pane. | |
| 233 | */ | |
| 234 | void newEditor() { | |
| 235 | final FileEditorTab tab = createFileEditor( getDefaultPath() ); | |
| 236 | ||
| 237 | getTabs().add( tab ); | |
| 238 | getSelectionModel().select( tab ); | |
| 239 | } | |
| 240 | ||
| 241 | void openFileDialog() { | |
| 242 | final String title = get( "Dialog.file.choose.open.title" ); | |
| 243 | final FileChooser dialog = createFileChooser( title ); | |
| 244 | final List<File> files = dialog.showOpenMultipleDialog( getWindow() ); | |
| 245 | ||
| 246 | if( files != null ) { | |
| 247 | openFiles( files ); | |
| 248 | } | |
| 249 | } | |
| 250 | ||
| 251 | /** | |
| 252 | * Opens the files into new editors, unless one of those files was a | |
| 253 | * definition file. The definition file is loaded into the definition pane, | |
| 254 | * but only the first one selected (multiple definition files will result in a | |
| 255 | * warning). | |
| 256 | * | |
| 257 | * @param files The list of non-definition files that the were requested to | |
| 258 | * open. | |
| 259 | */ | |
| 260 | private void openFiles( final List<File> files ) { | |
| 261 | final List<String> extensions = | |
| 262 | createExtensionFilter( DEFINITION ).getExtensions(); | |
| 263 | final var predicate = createFileTypePredicate( extensions ); | |
| 264 | ||
| 265 | // The user might have opened multiple definitions files. These will | |
| 266 | // be discarded from the text editable files. | |
| 267 | final var definitions | |
| 268 | = files.stream().filter( predicate ).collect( Collectors.toList() ); | |
| 269 | ||
| 270 | // Create a modifiable list to remove any definition files that were | |
| 271 | // opened. | |
| 272 | final var editors = new ArrayList<>( files ); | |
| 273 | ||
| 274 | if( !editors.isEmpty() ) { | |
| 275 | saveLastDirectory( editors.get( 0 ) ); | |
| 276 | } | |
| 277 | ||
| 278 | editors.removeAll( definitions ); | |
| 279 | ||
| 280 | // Open editor-friendly files (e.g,. Markdown, XML) in new tabs. | |
| 281 | if( !editors.isEmpty() ) { | |
| 282 | openEditors( editors, 0 ); | |
| 283 | } | |
| 284 | ||
| 285 | if( !definitions.isEmpty() ) { | |
| 286 | openDefinition( definitions.get( 0 ) ); | |
| 287 | } | |
| 288 | } | |
| 289 | ||
| 290 | private void openEditors( final List<File> files, final int activeIndex ) { | |
| 291 | final int fileTally = files.size(); | |
| 292 | final List<Tab> tabs = getTabs(); | |
| 293 | ||
| 294 | // Close single unmodified "Untitled" tab. | |
| 295 | if( tabs.size() == 1 ) { | |
| 296 | final FileEditorTab fileEditor = (FileEditorTab) (tabs.get( 0 )); | |
| 297 | ||
| 298 | if( fileEditor.getPath() == null && !fileEditor.isModified() ) { | |
| 299 | closeEditor( fileEditor, false ); | |
| 300 | } | |
| 301 | } | |
| 302 | ||
| 303 | for( int i = 0; i < fileTally; i++ ) { | |
| 304 | final Path path = files.get( i ).toPath(); | |
| 305 | ||
| 306 | FileEditorTab fileEditorTab = findEditor( path ); | |
| 307 | ||
| 308 | // Only open new files. | |
| 309 | if( fileEditorTab == null ) { | |
| 310 | fileEditorTab = createFileEditor( path ); | |
| 311 | getTabs().add( fileEditorTab ); | |
| 312 | } | |
| 313 | ||
| 314 | // Select the first file in the list. | |
| 315 | if( i == activeIndex ) { | |
| 316 | getSelectionModel().select( fileEditorTab ); | |
| 317 | } | |
| 318 | } | |
| 319 | } | |
| 320 | ||
| 321 | /** | |
| 322 | * Returns a property that changes when a new definition file is opened. | |
| 323 | * | |
| 324 | * @return The path to a definition file that was opened. | |
| 325 | */ | |
| 326 | public ReadOnlyObjectProperty<Path> onOpenDefinitionFileProperty() { | |
| 327 | return getOnOpenDefinitionFile().getReadOnlyProperty(); | |
| 328 | } | |
| 329 | ||
| 330 | private ReadOnlyObjectWrapper<Path> getOnOpenDefinitionFile() { | |
| 331 | return mOpenDefinition; | |
| 332 | } | |
| 333 | ||
| 334 | /** | |
| 335 | * Called when the user has opened a definition file (using the file open | |
| 336 | * dialog box). This will replace the current set of definitions for the | |
| 337 | * active tab. | |
| 338 | * | |
| 339 | * @param definition The file to open. | |
| 340 | */ | |
| 341 | private void openDefinition( final File definition ) { | |
| 342 | // TODO: Prevent reading this file twice when a new text document is opened. | |
| 343 | // (might be a matter of checking the value first). | |
| 344 | getOnOpenDefinitionFile().set( definition.toPath() ); | |
| 345 | } | |
| 346 | ||
| 347 | /** | |
| 348 | * Called when the contents of the editor are to be saved. | |
| 349 | * | |
| 350 | * @param tab The tab containing content to save. | |
| 351 | * @return true The contents were saved (or needn't be saved). | |
| 352 | */ | |
| 353 | public boolean saveEditor( final FileEditorTab tab ) { | |
| 354 | if( tab == null || !tab.isModified() ) { | |
| 355 | return true; | |
| 356 | } | |
| 357 | ||
| 358 | return tab.getPath() == null ? saveEditorAs( tab ) : tab.save(); | |
| 359 | } | |
| 360 | ||
| 361 | /** | |
| 362 | * Opens the Save As dialog for the user to save the content under a new | |
| 363 | * path. | |
| 364 | * | |
| 365 | * @param tab The tab with contents to save. | |
| 366 | * @return true The contents were saved, or the tab was null. | |
| 367 | */ | |
| 368 | public boolean saveEditorAs( final FileEditorTab tab ) { | |
| 369 | if( tab == null ) { | |
| 370 | return true; | |
| 371 | } | |
| 372 | ||
| 373 | getSelectionModel().select( tab ); | |
| 374 | ||
| 375 | final FileChooser fileChooser = createFileChooser( get( | |
| 376 | "Dialog.file.choose.save.title" ) ); | |
| 377 | final File file = fileChooser.showSaveDialog( getWindow() ); | |
| 378 | if( file == null ) { | |
| 379 | return false; | |
| 380 | } | |
| 381 | ||
| 382 | saveLastDirectory( file ); | |
| 383 | tab.setPath( file.toPath() ); | |
| 384 | ||
| 385 | return tab.save(); | |
| 386 | } | |
| 387 | ||
| 388 | void saveAllEditors() { | |
| 389 | for( final FileEditorTab fileEditor : getAllEditors() ) { | |
| 390 | saveEditor( fileEditor ); | |
| 391 | } | |
| 392 | } | |
| 393 | ||
| 394 | /** | |
| 395 | * Answers whether the file has had modifications. ' | |
| 396 | * | |
| 397 | * @param tab THe tab to check for modifications. | |
| 398 | * @return false The file is unmodified. | |
| 399 | */ | |
| 400 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") | |
| 401 | boolean canCloseEditor( final FileEditorTab tab ) { | |
| 402 | final AtomicReference<Boolean> canClose = new AtomicReference<>(); | |
| 403 | canClose.set( true ); | |
| 404 | ||
| 405 | if( tab.isModified() ) { | |
| 406 | final Notification message = getNotifyService().createNotification( | |
| 407 | Messages.get( "Alert.file.close.title" ), | |
| 408 | Messages.get( "Alert.file.close.text" ), | |
| 409 | tab.getText() | |
| 410 | ); | |
| 411 | ||
| 412 | final Alert confirmSave = getNotifyService().createConfirmation( | |
| 413 | getWindow(), message ); | |
| 414 | ||
| 415 | final Optional<ButtonType> buttonType = confirmSave.showAndWait(); | |
| 416 | ||
| 417 | buttonType.ifPresent( | |
| 418 | save -> canClose.set( | |
| 419 | save == YES ? saveEditor( tab ) : save == ButtonType.NO | |
| 420 | ) | |
| 421 | ); | |
| 422 | } | |
| 423 | ||
| 424 | return canClose.get(); | |
| 425 | } | |
| 426 | ||
| 427 | boolean closeEditor( final FileEditorTab tab, final boolean save ) { | |
| 428 | if( tab == null ) { | |
| 429 | return true; | |
| 430 | } | |
| 431 | ||
| 432 | if( save ) { | |
| 433 | Event event = new Event( tab, tab, Tab.TAB_CLOSE_REQUEST_EVENT ); | |
| 434 | Event.fireEvent( tab, event ); | |
| 435 | ||
| 436 | if( event.isConsumed() ) { | |
| 437 | return false; | |
| 438 | } | |
| 439 | } | |
| 440 | ||
| 441 | getTabs().remove( tab ); | |
| 442 | ||
| 443 | if( tab.getOnClosed() != null ) { | |
| 444 | Event.fireEvent( tab, new Event( Tab.CLOSED_EVENT ) ); | |
| 445 | } | |
| 446 | ||
| 447 | return true; | |
| 448 | } | |
| 449 | ||
| 450 | boolean closeAllEditors() { | |
| 451 | final FileEditorTab[] allEditors = getAllEditors(); | |
| 452 | final FileEditorTab activeEditor = getActiveFileEditor(); | |
| 453 | ||
| 454 | // try to save active tab first because in case the user decides to cancel, | |
| 455 | // then it stays active | |
| 456 | if( activeEditor != null && !canCloseEditor( activeEditor ) ) { | |
| 457 | return false; | |
| 458 | } | |
| 459 | ||
| 460 | // This should be called any time a tab changes. | |
| 461 | persistPreferences(); | |
| 462 | ||
| 463 | // save modified tabs | |
| 464 | for( int i = 0; i < allEditors.length; i++ ) { | |
| 465 | final FileEditorTab fileEditor = allEditors[ i ]; | |
| 466 | ||
| 467 | if( fileEditor == activeEditor ) { | |
| 468 | continue; | |
| 469 | } | |
| 470 | ||
| 471 | if( fileEditor.isModified() ) { | |
| 472 | // activate the modified tab to make its modified content visible to | |
| 473 | // the user | |
| 474 | getSelectionModel().select( i ); | |
| 475 | ||
| 476 | if( !canCloseEditor( fileEditor ) ) { | |
| 477 | return false; | |
| 478 | } | |
| 479 | } | |
| 480 | } | |
| 481 | ||
| 482 | // Close all tabs. | |
| 483 | for( final FileEditorTab fileEditor : allEditors ) { | |
| 484 | if( !closeEditor( fileEditor, false ) ) { | |
| 485 | return false; | |
| 486 | } | |
| 487 | } | |
| 488 | ||
| 489 | return getTabs().isEmpty(); | |
| 490 | } | |
| 491 | ||
| 492 | private FileEditorTab[] getAllEditors() { | |
| 493 | final ObservableList<Tab> tabs = getTabs(); | |
| 494 | final int length = tabs.size(); | |
| 495 | final FileEditorTab[] allEditors = new FileEditorTab[ length ]; | |
| 496 | ||
| 497 | for( int i = 0; i < length; i++ ) { | |
| 498 | allEditors[ i ] = (FileEditorTab) tabs.get( i ); | |
| 499 | } | |
| 500 | ||
| 501 | return allEditors; | |
| 502 | } | |
| 503 | ||
| 504 | /** | |
| 505 | * Returns the file editor tab that has the given path. | |
| 506 | * | |
| 507 | * @return null No file editor tab for the given path was found. | |
| 508 | */ | |
| 509 | private FileEditorTab findEditor( final Path path ) { | |
| 510 | for( final Tab tab : getTabs() ) { | |
| 511 | final FileEditorTab fileEditor = (FileEditorTab) tab; | |
| 512 | ||
| 513 | if( fileEditor.isPath( path ) ) { | |
| 514 | return fileEditor; | |
| 515 | } | |
| 516 | } | |
| 517 | ||
| 518 | return null; | |
| 519 | } | |
| 520 | ||
| 521 | private FileChooser createFileChooser( String title ) { | |
| 522 | final FileChooser fileChooser = new FileChooser(); | |
| 523 | ||
| 524 | fileChooser.setTitle( title ); | |
| 525 | fileChooser.getExtensionFilters().addAll( | |
| 526 | createExtensionFilters() ); | |
| 527 | ||
| 528 | final String lastDirectory = getPreferences().get( "lastDirectory", null ); | |
| 529 | File file = new File( (lastDirectory != null) ? lastDirectory : "." ); | |
| 530 | ||
| 531 | if( !file.isDirectory() ) { | |
| 532 | file = new File( "." ); | |
| 533 | } | |
| 534 | ||
| 535 | fileChooser.setInitialDirectory( file ); | |
| 536 | return fileChooser; | |
| 537 | } | |
| 538 | ||
| 539 | private List<ExtensionFilter> createExtensionFilters() { | |
| 540 | final List<ExtensionFilter> list = new ArrayList<>(); | |
| 541 | ||
| 542 | // TODO: Return a list of all properties that match the filter prefix. | |
| 543 | // This will allow dynamic filters to be added and removed just by | |
| 544 | // updating the properties file. | |
| 545 | list.add( createExtensionFilter( ALL ) ); | |
| 546 | list.add( createExtensionFilter( SOURCE ) ); | |
| 547 | list.add( createExtensionFilter( DEFINITION ) ); | |
| 548 | list.add( createExtensionFilter( XML ) ); | |
| 549 | return list; | |
| 550 | } | |
| 551 | ||
| 552 | /** | |
| 553 | * Returns a filter for file name extensions recognized by the application | |
| 554 | * that can be opened by the user. | |
| 555 | * | |
| 556 | * @param filetype Used to find the globbing pattern for extensions. | |
| 557 | * @return A filename filter suitable for use by a FileDialog instance. | |
| 558 | */ | |
| 559 | private ExtensionFilter createExtensionFilter( final FileType filetype ) { | |
| 560 | final String tKey = String.format( "%s.title.%s", | |
| 561 | FILTER_EXTENSION_TITLES, | |
| 562 | filetype ); | |
| 563 | final String eKey = String.format( "%s.%s", GLOB_PREFIX_FILE, filetype ); | |
| 564 | ||
| 565 | return new ExtensionFilter( Messages.get( tKey ), getExtensions( eKey ) ); | |
| 566 | } | |
| 567 | ||
| 568 | private void saveLastDirectory( final File file ) { | |
| 569 | getPreferences().put( "lastDirectory", file.getParent() ); | |
| 570 | } | |
| 571 | ||
| 572 | public void initPreferences() { | |
| 573 | int activeIndex = 0; | |
| 574 | ||
| 575 | final Preferences preferences = getPreferences(); | |
| 576 | final String[] fileNames = Utils.getPrefsStrings( preferences, "file" ); | |
| 577 | final String activeFileName = preferences.get( "activeFile", null ); | |
| 578 | ||
| 579 | final List<File> files = new ArrayList<>( fileNames.length ); | |
| 580 | ||
| 581 | for( final String fileName : fileNames ) { | |
| 582 | final File file = new File( fileName ); | |
| 583 | ||
| 584 | if( file.exists() ) { | |
| 585 | files.add( file ); | |
| 586 | ||
| 587 | if( fileName.equals( activeFileName ) ) { | |
| 588 | activeIndex = files.size() - 1; | |
| 589 | } | |
| 590 | } | |
| 591 | } | |
| 592 | ||
| 593 | if( files.isEmpty() ) { | |
| 594 | newEditor(); | |
| 595 | } | |
| 596 | else { | |
| 597 | openEditors( files, activeIndex ); | |
| 598 | } | |
| 599 | } | |
| 600 | ||
| 601 | public void persistPreferences() { | |
| 602 | final ObservableList<Tab> allEditors = getTabs(); | |
| 603 | final List<String> fileNames = new ArrayList<>( allEditors.size() ); | |
| 604 | ||
| 605 | for( final Tab tab : allEditors ) { | |
| 606 | final FileEditorTab fileEditor = (FileEditorTab) tab; | |
| 607 | final Path filePath = fileEditor.getPath(); | |
| 608 | ||
| 609 | if( filePath != null ) { | |
| 610 | fileNames.add( filePath.toString() ); | |
| 611 | } | |
| 612 | } | |
| 613 | ||
| 614 | final Preferences preferences = getPreferences(); | |
| 615 | Utils.putPrefsStrings( preferences, | |
| 616 | "file", | |
| 617 | fileNames.toArray( new String[ 0 ] ) ); | |
| 618 | ||
| 619 | final FileEditorTab activeEditor = getActiveFileEditor(); | |
| 620 | final Path filePath = activeEditor == null ? null : activeEditor.getPath(); | |
| 621 | ||
| 622 | if( filePath == null ) { | |
| 623 | preferences.remove( "activeFile" ); | |
| 624 | } | |
| 625 | else { | |
| 626 | preferences.put( "activeFile", filePath.toString() ); | |
| 627 | } | |
| 628 | } | |
| 629 | ||
| 630 | private List<String> getExtensions( final String key ) { | |
| 631 | return getSettings().getStringSettingList( key ); | |
| 632 | } | |
| 633 | ||
| 634 | private Notifier getNotifyService() { | |
| 635 | return sNotifier; | |
| 636 | } | |
| 637 | ||
| 638 | private Settings getSettings() { | |
| 639 | return SETTINGS; | |
| 640 | } | |
| 641 | ||
| 642 | protected Options getOptions() { | |
| 643 | return sOptions; | |
| 644 | } | |
| 645 | ||
| 646 | private Window getWindow() { | |
| 647 | return getScene().getWindow(); | |
| 648 | } | |
| 649 | ||
| 650 | private Preferences getPreferences() { | |
| 651 | return getOptions().getState(); | |
| 652 | } | |
| 653 | ||
| 654 | Node getNode() { | |
| 655 | return this; | |
| 656 | } | |
| 657 | } | |
| 1 | 658 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents different file type classifications. These are high-level mappings | |
| 32 | * that correspond to the list of glob patterns found within {@code | |
| 33 | * settings.properties}. | |
| 34 | */ | |
| 35 | public enum FileType { | |
| 36 | ||
| 37 | ALL( "all" ), | |
| 38 | RMARKDOWN( "rmarkdown" ), | |
| 39 | RXML( "rxml" ), | |
| 40 | SOURCE( "source" ), | |
| 41 | DEFINITION( "definition" ), | |
| 42 | XML( "xml" ), | |
| 43 | CSV( "csv" ), | |
| 44 | JSON( "json" ), | |
| 45 | TOML( "toml" ), | |
| 46 | YAML( "yaml" ), | |
| 47 | PROPERTIES( "properties" ), | |
| 48 | UNKNOWN( "unknown" ); | |
| 49 | ||
| 50 | private final String mType; | |
| 51 | ||
| 52 | /** | |
| 53 | * Default constructor for enumerated file type. | |
| 54 | * | |
| 55 | * @param type Human-readable name for the file type. | |
| 56 | */ | |
| 57 | FileType( final String type ) { | |
| 58 | mType = type; | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Returns the file type that corresponds to the given string. | |
| 63 | * | |
| 64 | * @param type The string to compare against this enumeration of file types. | |
| 65 | * @return The corresponding File Type for the given string. | |
| 66 | * @throws IllegalArgumentException Type not found. | |
| 67 | */ | |
| 68 | public static FileType from( final String type ) { | |
| 69 | for( final FileType fileType : FileType.values() ) { | |
| 70 | if( fileType.isType( type ) ) { | |
| 71 | return fileType; | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | throw new IllegalArgumentException( type ); | |
| 76 | } | |
| 77 | ||
| 78 | /** | |
| 79 | * Answers whether this file type matches the given string, case insensitive | |
| 80 | * comparison. | |
| 81 | * | |
| 82 | * @param type Presumably a file name extension to check against. | |
| 83 | * @return true The given extension corresponds to this enumerated type. | |
| 84 | */ | |
| 85 | public boolean isType( final String type ) { | |
| 86 | return getType().equalsIgnoreCase( type ); | |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Returns the human-readable name for the file type. | |
| 91 | * | |
| 92 | * @return A non-null instance. | |
| 93 | */ | |
| 94 | private String getType() { | |
| 95 | return mType; | |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * Returns the lowercase version of the file name extension. | |
| 100 | * | |
| 101 | * @return The file name, in lower case. | |
| 102 | */ | |
| 103 | @Override | |
| 104 | public String toString() { | |
| 105 | return getType(); | |
| 106 | } | |
| 107 | } | |
| 1 | 108 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import java.io.IOException; | |
| 31 | import java.io.InputStream; | |
| 32 | import java.util.Calendar; | |
| 33 | import java.util.Properties; | |
| 34 | ||
| 35 | import static java.lang.String.format; | |
| 36 | ||
| 37 | /** | |
| 38 | * Launches the application using the {@link Main} class. | |
| 39 | * | |
| 40 | * <p> | |
| 41 | * This is required until modules are implemented, which may never happen | |
| 42 | * because the application should be ported away from Java and JavaFX. | |
| 43 | * </p> | |
| 44 | */ | |
| 45 | public class Launcher { | |
| 46 | /** | |
| 47 | * Delegates to the application entry point. | |
| 48 | * | |
| 49 | * @param args Command-line arguments. | |
| 50 | */ | |
| 51 | public static void main( final String[] args ) throws IOException { | |
| 52 | showAppInfo(); | |
| 53 | Main.main( args ); | |
| 54 | } | |
| 55 | ||
| 56 | @SuppressWarnings("RedundantStringFormatCall") | |
| 57 | private static void showAppInfo() throws IOException { | |
| 58 | out( format( "%s version %s", getTitle(), getVersion() ) ); | |
| 59 | out( format( "Copyright %s White Magic Software, Ltd.", getYear() ) ); | |
| 60 | out( format( "Portions copyright 2020 Karl Tauber." ) ); | |
| 61 | } | |
| 62 | ||
| 63 | private static void out( final String s ) { | |
| 64 | System.out.println( s ); | |
| 65 | } | |
| 66 | ||
| 67 | private static String getTitle() throws IOException { | |
| 68 | final Properties properties = loadProperties( "messages.properties" ); | |
| 69 | return properties.getProperty( "Main.title" ); | |
| 70 | } | |
| 71 | ||
| 72 | private static String getVersion() throws IOException { | |
| 73 | final Properties properties = loadProperties( "app.properties" ); | |
| 74 | return properties.getProperty( "application.version" ); | |
| 75 | } | |
| 76 | ||
| 77 | private static String getYear() { | |
| 78 | return Integer.toString( Calendar.getInstance().get( Calendar.YEAR ) ); | |
| 79 | } | |
| 80 | ||
| 81 | @SuppressWarnings("SameParameterValue") | |
| 82 | private static Properties loadProperties( final String resource ) | |
| 83 | throws IOException { | |
| 84 | final Properties properties = new Properties(); | |
| 85 | properties.load( getResourceAsStream( getResourceName( resource ) ) ); | |
| 86 | return properties; | |
| 87 | } | |
| 88 | ||
| 89 | private static String getResourceName( final String resource ) { | |
| 90 | return format( "%s/%s", getPackagePath(), resource ); | |
| 91 | } | |
| 92 | ||
| 93 | private static String getPackagePath() { | |
| 94 | return Launcher.class.getPackageName().replace( '.', '/' ); | |
| 95 | } | |
| 96 | ||
| 97 | private static InputStream getResourceAsStream( final String resource ) { | |
| 98 | return Launcher.class.getClassLoader().getResourceAsStream( resource ); | |
| 99 | } | |
| 100 | } | |
| 1 | 101 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.preferences.FilePreferencesFactory; | |
| 31 | import com.scrivenvar.service.Options; | |
| 32 | import com.scrivenvar.service.Snitch; | |
| 33 | import com.scrivenvar.util.ResourceWalker; | |
| 34 | import com.scrivenvar.util.StageState; | |
| 35 | import javafx.application.Application; | |
| 36 | import javafx.scene.Scene; | |
| 37 | import javafx.scene.image.Image; | |
| 38 | import javafx.stage.Stage; | |
| 39 | ||
| 40 | import java.awt.*; | |
| 41 | import java.io.FileInputStream; | |
| 42 | import java.io.IOException; | |
| 43 | import java.io.InputStream; | |
| 44 | import java.net.URI; | |
| 45 | import java.util.Map; | |
| 46 | import java.util.logging.LogManager; | |
| 47 | ||
| 48 | import static com.scrivenvar.Constants.*; | |
| 49 | import static com.scrivenvar.Messages.get; | |
| 50 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 51 | import static java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment; | |
| 52 | import static java.awt.font.TextAttribute.*; | |
| 53 | import static javafx.scene.input.KeyCode.F11; | |
| 54 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 55 | ||
| 56 | /** | |
| 57 | * Application entry point. The application allows users to edit Markdown | |
| 58 | * files and see a real-time preview of the edits. | |
| 59 | */ | |
| 60 | public final class Main extends Application { | |
| 61 | ||
| 62 | static { | |
| 63 | // Suppress logging to standard output. | |
| 64 | LogManager.getLogManager().reset(); | |
| 65 | ||
| 66 | // Suppress logging to standard error. | |
| 67 | System.err.close(); | |
| 68 | } | |
| 69 | ||
| 70 | private final Options mOptions = Services.load( Options.class ); | |
| 71 | private final Snitch mSnitch = Services.load( Snitch.class ); | |
| 72 | ||
| 73 | private final Thread mSnitchThread = new Thread( getSnitch() ); | |
| 74 | private final MainWindow mMainWindow = new MainWindow(); | |
| 75 | ||
| 76 | @SuppressWarnings({"FieldCanBeLocal", "unused"}) | |
| 77 | private StageState mStageState; | |
| 78 | ||
| 79 | /** | |
| 80 | * Application entry point. | |
| 81 | * | |
| 82 | * @param args Command-line arguments. | |
| 83 | */ | |
| 84 | public static void main( final String[] args ) { | |
| 85 | initPreferences(); | |
| 86 | initFonts(); | |
| 87 | launch( args ); | |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * JavaFX entry point. | |
| 92 | * | |
| 93 | * @param stage The primary application stage. | |
| 94 | */ | |
| 95 | @Override | |
| 96 | public void start( final Stage stage ) { | |
| 97 | initState( stage ); | |
| 98 | initStage( stage ); | |
| 99 | initSnitch(); | |
| 100 | ||
| 101 | stage.show(); | |
| 102 | ||
| 103 | // After the stage is visible, the panel dimensions are | |
| 104 | // known, which allows scaling images to fit the preview panel. | |
| 105 | getMainWindow().init(); | |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * This needs to run before the windowing system kicks in, otherwise the | |
| 110 | * fonts will not be found. | |
| 111 | */ | |
| 112 | @SuppressWarnings({"rawtypes", "unchecked"}) | |
| 113 | public static void initFonts() { | |
| 114 | final var ge = getLocalGraphicsEnvironment(); | |
| 115 | ||
| 116 | try { | |
| 117 | ResourceWalker.walk( | |
| 118 | FONT_DIRECTORY, path -> { | |
| 119 | final var uri = path.toUri(); | |
| 120 | final var filename = path.toString(); | |
| 121 | ||
| 122 | try( final var is = openFont( uri, filename ) ) { | |
| 123 | final var font = Font.createFont( Font.TRUETYPE_FONT, is ); | |
| 124 | final Map attributes = font.getAttributes(); | |
| 125 | ||
| 126 | attributes.put( LIGATURES, LIGATURES_ON ); | |
| 127 | attributes.put( KERNING, KERNING_ON ); | |
| 128 | ge.registerFont( font.deriveFont( attributes ) ); | |
| 129 | } catch( final Exception e ) { | |
| 130 | alert( e ); | |
| 131 | } | |
| 132 | } | |
| 133 | ); | |
| 134 | } catch( final Exception e ) { | |
| 135 | alert( e ); | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | private static InputStream openFont( final URI uri, final String filename ) | |
| 140 | throws IOException { | |
| 141 | return uri.getScheme().equals( "jar" ) | |
| 142 | ? Main.class.getResourceAsStream( filename ) | |
| 143 | : new FileInputStream( filename ); | |
| 144 | } | |
| 145 | ||
| 146 | /** | |
| 147 | * Sets the factory used for reading user preferences. | |
| 148 | */ | |
| 149 | private static void initPreferences() { | |
| 150 | System.setProperty( | |
| 151 | "java.util.prefs.PreferencesFactory", | |
| 152 | FilePreferencesFactory.class.getName() | |
| 153 | ); | |
| 154 | } | |
| 155 | ||
| 156 | private void initState( final Stage stage ) { | |
| 157 | mStageState = new StageState( stage, getOptions().getState() ); | |
| 158 | } | |
| 159 | ||
| 160 | private void initStage( final Stage stage ) { | |
| 161 | stage.getIcons().addAll( | |
| 162 | createImage( FILE_LOGO_16 ), | |
| 163 | createImage( FILE_LOGO_32 ), | |
| 164 | createImage( FILE_LOGO_128 ), | |
| 165 | createImage( FILE_LOGO_256 ), | |
| 166 | createImage( FILE_LOGO_512 ) ); | |
| 167 | stage.setTitle( getApplicationTitle() ); | |
| 168 | stage.setScene( getScene() ); | |
| 169 | ||
| 170 | stage.addEventHandler( KEY_PRESSED, event -> { | |
| 171 | if( F11.equals( event.getCode() ) ) { | |
| 172 | stage.setFullScreen( !stage.isFullScreen() ); | |
| 173 | } | |
| 174 | } ); | |
| 175 | } | |
| 176 | ||
| 177 | /** | |
| 178 | * Watch for file system changes. | |
| 179 | */ | |
| 180 | private void initSnitch() { | |
| 181 | getSnitchThread().start(); | |
| 182 | } | |
| 183 | ||
| 184 | /** | |
| 185 | * Stops the snitch service, if its running. | |
| 186 | * | |
| 187 | * @throws InterruptedException Couldn't stop the snitch thread. | |
| 188 | */ | |
| 189 | @Override | |
| 190 | public void stop() throws InterruptedException { | |
| 191 | getSnitch().stop(); | |
| 192 | ||
| 193 | final Thread thread = getSnitchThread(); | |
| 194 | thread.interrupt(); | |
| 195 | thread.join(); | |
| 196 | } | |
| 197 | ||
| 198 | private Snitch getSnitch() { | |
| 199 | return mSnitch; | |
| 200 | } | |
| 201 | ||
| 202 | private Thread getSnitchThread() { | |
| 203 | return mSnitchThread; | |
| 204 | } | |
| 205 | ||
| 206 | private Options getOptions() { | |
| 207 | return mOptions; | |
| 208 | } | |
| 209 | ||
| 210 | private MainWindow getMainWindow() { | |
| 211 | return mMainWindow; | |
| 212 | } | |
| 213 | ||
| 214 | private Scene getScene() { | |
| 215 | return getMainWindow().getScene(); | |
| 216 | } | |
| 217 | ||
| 218 | private String getApplicationTitle() { | |
| 219 | return get( "Main.title" ); | |
| 220 | } | |
| 221 | ||
| 222 | private Image createImage( final String filename ) { | |
| 223 | return new Image( filename ); | |
| 224 | } | |
| 225 | } | |
| 1 | 226 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.dlsc.preferencesfx.PreferencesFxEvent; | |
| 31 | import com.scrivenvar.definition.DefinitionFactory; | |
| 32 | import com.scrivenvar.definition.DefinitionPane; | |
| 33 | import com.scrivenvar.definition.DefinitionSource; | |
| 34 | import com.scrivenvar.definition.MapInterpolator; | |
| 35 | import com.scrivenvar.definition.yaml.YamlDefinitionSource; | |
| 36 | import com.scrivenvar.editors.DefinitionNameInjector; | |
| 37 | import com.scrivenvar.editors.EditorPane; | |
| 38 | import com.scrivenvar.editors.markdown.MarkdownEditorPane; | |
| 39 | import com.scrivenvar.preferences.UserPreferences; | |
| 40 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 41 | import com.scrivenvar.processors.HtmlPreviewProcessor; | |
| 42 | import com.scrivenvar.processors.Processor; | |
| 43 | import com.scrivenvar.processors.ProcessorFactory; | |
| 44 | import com.scrivenvar.service.Options; | |
| 45 | import com.scrivenvar.service.Snitch; | |
| 46 | import com.scrivenvar.spelling.api.SpellCheckListener; | |
| 47 | import com.scrivenvar.spelling.api.SpellChecker; | |
| 48 | import com.scrivenvar.spelling.impl.PermissiveSpeller; | |
| 49 | import com.scrivenvar.spelling.impl.SymSpellSpeller; | |
| 50 | import com.scrivenvar.util.Action; | |
| 51 | import com.scrivenvar.util.ActionBuilder; | |
| 52 | import com.scrivenvar.util.ActionUtils; | |
| 53 | import com.vladsch.flexmark.parser.Parser; | |
| 54 | import com.vladsch.flexmark.util.ast.NodeVisitor; | |
| 55 | import com.vladsch.flexmark.util.ast.VisitHandler; | |
| 56 | import javafx.beans.binding.Bindings; | |
| 57 | import javafx.beans.binding.BooleanBinding; | |
| 58 | import javafx.beans.property.BooleanProperty; | |
| 59 | import javafx.beans.property.SimpleBooleanProperty; | |
| 60 | import javafx.beans.value.ChangeListener; | |
| 61 | import javafx.beans.value.ObservableBooleanValue; | |
| 62 | import javafx.beans.value.ObservableValue; | |
| 63 | import javafx.collections.ListChangeListener.Change; | |
| 64 | import javafx.collections.ObservableList; | |
| 65 | import javafx.event.Event; | |
| 66 | import javafx.event.EventHandler; | |
| 67 | import javafx.geometry.Pos; | |
| 68 | import javafx.scene.Node; | |
| 69 | import javafx.scene.Scene; | |
| 70 | import javafx.scene.control.*; | |
| 71 | import javafx.scene.control.Alert.AlertType; | |
| 72 | import javafx.scene.image.Image; | |
| 73 | import javafx.scene.image.ImageView; | |
| 74 | import javafx.scene.input.Clipboard; | |
| 75 | import javafx.scene.input.ClipboardContent; | |
| 76 | import javafx.scene.input.KeyEvent; | |
| 77 | import javafx.scene.layout.BorderPane; | |
| 78 | import javafx.scene.layout.VBox; | |
| 79 | import javafx.scene.text.Text; | |
| 80 | import javafx.stage.Window; | |
| 81 | import javafx.stage.WindowEvent; | |
| 82 | import javafx.util.Duration; | |
| 83 | import org.apache.commons.lang3.SystemUtils; | |
| 84 | import org.controlsfx.control.StatusBar; | |
| 85 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 86 | import org.fxmisc.richtext.model.StyleSpansBuilder; | |
| 87 | import org.reactfx.value.Val; | |
| 88 | ||
| 89 | import java.io.BufferedReader; | |
| 90 | import java.io.InputStreamReader; | |
| 91 | import java.nio.file.Path; | |
| 92 | import java.nio.file.Paths; | |
| 93 | import java.util.*; | |
| 94 | import java.util.concurrent.atomic.AtomicInteger; | |
| 95 | import java.util.function.Consumer; | |
| 96 | import java.util.function.Function; | |
| 97 | import java.util.prefs.Preferences; | |
| 98 | import java.util.stream.Collectors; | |
| 99 | ||
| 100 | import static com.scrivenvar.Constants.*; | |
| 101 | import static com.scrivenvar.Messages.get; | |
| 102 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 103 | import static com.scrivenvar.util.StageState.*; | |
| 104 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*; | |
| 105 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 106 | import static java.util.Collections.emptyList; | |
| 107 | import static java.util.Collections.singleton; | |
| 108 | import static javafx.application.Platform.runLater; | |
| 109 | import static javafx.event.Event.fireEvent; | |
| 110 | import static javafx.scene.input.KeyCode.ENTER; | |
| 111 | import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST; | |
| 112 | import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward; | |
| 113 | ||
| 114 | /** | |
| 115 | * Main window containing a tab pane in the center for file editors. | |
| 116 | */ | |
| 117 | public class MainWindow implements Observer { | |
| 118 | /** | |
| 119 | * The {@code OPTIONS} variable must be declared before all other variables | |
| 120 | * to prevent subsequent initializations from failing due to missing user | |
| 121 | * preferences. | |
| 122 | */ | |
| 123 | private static final Options sOptions = Services.load( Options.class ); | |
| 124 | private static final Snitch SNITCH = Services.load( Snitch.class ); | |
| 125 | ||
| 126 | private final Scene mScene; | |
| 127 | private final StatusBar mStatusBar; | |
| 128 | private final Text mLineNumberText; | |
| 129 | private final TextField mFindTextField; | |
| 130 | private final SpellChecker mSpellChecker; | |
| 131 | ||
| 132 | private final Object mMutex = new Object(); | |
| 133 | ||
| 134 | /** | |
| 135 | * Prevents re-instantiation of processing classes. | |
| 136 | */ | |
| 137 | private final Map<FileEditorTab, Processor<String>> mProcessors = | |
| 138 | new HashMap<>(); | |
| 139 | ||
| 140 | private final Map<String, String> mResolvedMap = | |
| 141 | new HashMap<>( DEFAULT_MAP_SIZE ); | |
| 142 | ||
| 143 | private final EventHandler<PreferencesFxEvent> mRPreferencesListener = | |
| 144 | event -> rerender(); | |
| 145 | ||
| 146 | /** | |
| 147 | * Called when the definition data is changed. | |
| 148 | */ | |
| 149 | private final EventHandler<TreeItem.TreeModificationEvent<Event>> | |
| 150 | mTreeHandler = event -> { | |
| 151 | exportDefinitions( getDefinitionPath() ); | |
| 152 | interpolateResolvedMap(); | |
| 153 | rerender(); | |
| 154 | }; | |
| 155 | ||
| 156 | /** | |
| 157 | * Called to inject the selected item when the user presses ENTER in the | |
| 158 | * definition pane. | |
| 159 | */ | |
| 160 | private final EventHandler<? super KeyEvent> mDefinitionKeyHandler = | |
| 161 | event -> { | |
| 162 | if( event.getCode() == ENTER ) { | |
| 163 | getVariableNameInjector().injectSelectedItem(); | |
| 164 | } | |
| 165 | }; | |
| 166 | ||
| 167 | private final ChangeListener<Integer> mCaretPositionListener = | |
| 168 | ( observable, oldPosition, newPosition ) -> { | |
| 169 | final FileEditorTab tab = getActiveFileEditorTab(); | |
| 170 | final EditorPane pane = tab.getEditorPane(); | |
| 171 | final StyleClassedTextArea editor = pane.getEditor(); | |
| 172 | ||
| 173 | getLineNumberText().setText( | |
| 174 | get( STATUS_BAR_LINE, | |
| 175 | editor.getCurrentParagraph() + 1, | |
| 176 | editor.getParagraphs().size(), | |
| 177 | editor.getCaretPosition() | |
| 178 | ) | |
| 179 | ); | |
| 180 | }; | |
| 181 | ||
| 182 | private final ChangeListener<Integer> mCaretParagraphListener = | |
| 183 | ( observable, oldIndex, newIndex ) -> | |
| 184 | scrollToParagraph( newIndex, true ); | |
| 185 | ||
| 186 | private DefinitionSource mDefinitionSource = createDefaultDefinitionSource(); | |
| 187 | private final DefinitionPane mDefinitionPane = new DefinitionPane(); | |
| 188 | private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane(); | |
| 189 | private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane( | |
| 190 | mCaretPositionListener, | |
| 191 | mCaretParagraphListener ); | |
| 192 | ||
| 193 | /** | |
| 194 | * Listens on the definition pane for double-click events. | |
| 195 | */ | |
| 196 | private final DefinitionNameInjector mVariableNameInjector | |
| 197 | = new DefinitionNameInjector( mDefinitionPane ); | |
| 198 | ||
| 199 | public MainWindow() { | |
| 200 | mStatusBar = createStatusBar(); | |
| 201 | mLineNumberText = createLineNumberText(); | |
| 202 | mFindTextField = createFindTextField(); | |
| 203 | mScene = createScene(); | |
| 204 | mSpellChecker = createSpellChecker(); | |
| 205 | ||
| 206 | // Add the close request listener before the window is shown. | |
| 207 | initLayout(); | |
| 208 | StatusBarNotifier.setStatusBar( mStatusBar ); | |
| 209 | } | |
| 210 | ||
| 211 | /** | |
| 212 | * Called after the stage is shown. | |
| 213 | */ | |
| 214 | public void init() { | |
| 215 | initFindInput(); | |
| 216 | initSnitch(); | |
| 217 | initDefinitionListener(); | |
| 218 | initTabAddedListener(); | |
| 219 | initTabChangedListener(); | |
| 220 | initPreferences(); | |
| 221 | initVariableNameInjector(); | |
| 222 | } | |
| 223 | ||
| 224 | private void initLayout() { | |
| 225 | final var scene = getScene(); | |
| 226 | ||
| 227 | scene.getStylesheets().add( STYLESHEET_SCENE ); | |
| 228 | scene.windowProperty().addListener( | |
| 229 | ( unused, oldWindow, newWindow ) -> | |
| 230 | newWindow.setOnCloseRequest( | |
| 231 | e -> { | |
| 232 | if( !getFileEditorPane().closeAllEditors() ) { | |
| 233 | e.consume(); | |
| 234 | } | |
| 235 | } | |
| 236 | ) | |
| 237 | ); | |
| 238 | } | |
| 239 | ||
| 240 | /** | |
| 241 | * Initialize the find input text field to listen on F3, ENTER, and | |
| 242 | * ESCAPE key presses. | |
| 243 | */ | |
| 244 | private void initFindInput() { | |
| 245 | final TextField input = getFindTextField(); | |
| 246 | ||
| 247 | input.setOnKeyPressed( ( KeyEvent event ) -> { | |
| 248 | switch( event.getCode() ) { | |
| 249 | case F3: | |
| 250 | case ENTER: | |
| 251 | editFindNext(); | |
| 252 | break; | |
| 253 | case F: | |
| 254 | if( !event.isControlDown() ) { | |
| 255 | break; | |
| 256 | } | |
| 257 | case ESCAPE: | |
| 258 | getStatusBar().setGraphic( null ); | |
| 259 | getActiveFileEditorTab().getEditorPane().requestFocus(); | |
| 260 | break; | |
| 261 | } | |
| 262 | } ); | |
| 263 | ||
| 264 | // Remove when the input field loses focus. | |
| 265 | input.focusedProperty().addListener( | |
| 266 | ( focused, oldFocus, newFocus ) -> { | |
| 267 | if( !newFocus ) { | |
| 268 | getStatusBar().setGraphic( null ); | |
| 269 | } | |
| 270 | } | |
| 271 | ); | |
| 272 | } | |
| 273 | ||
| 274 | /** | |
| 275 | * Watch for changes to external files. In particular, this awaits | |
| 276 | * modifications to any XSL files associated with XML files being edited. | |
| 277 | * When | |
| 278 | * an XSL file is modified (external to the application), the snitch's ears | |
| 279 | * perk up and the file is reloaded. This keeps the XSL transformation up to | |
| 280 | * date with what's on the file system. | |
| 281 | */ | |
| 282 | private void initSnitch() { | |
| 283 | SNITCH.addObserver( this ); | |
| 284 | } | |
| 285 | ||
| 286 | /** | |
| 287 | * Listen for {@link FileEditorTabPane} to receive open definition file | |
| 288 | * event. | |
| 289 | */ | |
| 290 | private void initDefinitionListener() { | |
| 291 | getFileEditorPane().onOpenDefinitionFileProperty().addListener( | |
| 292 | ( final ObservableValue<? extends Path> file, | |
| 293 | final Path oldPath, final Path newPath ) -> { | |
| 294 | openDefinitions( newPath ); | |
| 295 | rerender(); | |
| 296 | } | |
| 297 | ); | |
| 298 | } | |
| 299 | ||
| 300 | /** | |
| 301 | * Re-instantiates all processors then re-renders the active tab. This | |
| 302 | * will refresh the resolved map, force R to re-initialize, and brute-force | |
| 303 | * XSLT file reloads. | |
| 304 | */ | |
| 305 | private void rerender() { | |
| 306 | runLater( | |
| 307 | () -> { | |
| 308 | resetProcessors(); | |
| 309 | renderActiveTab(); | |
| 310 | } | |
| 311 | ); | |
| 312 | } | |
| 313 | ||
| 314 | /** | |
| 315 | * When tabs are added, hook the various change listeners onto the new | |
| 316 | * tab sothat the preview pane refreshes as necessary. | |
| 317 | */ | |
| 318 | private void initTabAddedListener() { | |
| 319 | final FileEditorTabPane editorPane = getFileEditorPane(); | |
| 320 | ||
| 321 | // Make sure the text processor kicks off when new files are opened. | |
| 322 | final ObservableList<Tab> tabs = editorPane.getTabs(); | |
| 323 | ||
| 324 | // Update the preview pane on tab changes. | |
| 325 | tabs.addListener( | |
| 326 | ( final Change<? extends Tab> change ) -> { | |
| 327 | while( change.next() ) { | |
| 328 | if( change.wasAdded() ) { | |
| 329 | // Multiple tabs can be added simultaneously. | |
| 330 | for( final Tab newTab : change.getAddedSubList() ) { | |
| 331 | final FileEditorTab tab = (FileEditorTab) newTab; | |
| 332 | ||
| 333 | initTextChangeListener( tab ); | |
| 334 | initScrollEventListener( tab ); | |
| 335 | initSpellCheckListener( tab ); | |
| 336 | // initSyntaxListener( tab ); | |
| 337 | } | |
| 338 | } | |
| 339 | } | |
| 340 | } | |
| 341 | ); | |
| 342 | } | |
| 343 | ||
| 344 | private void initTextChangeListener( final FileEditorTab tab ) { | |
| 345 | tab.addTextChangeListener( | |
| 346 | ( __, ov, nv ) -> { | |
| 347 | process( tab ); | |
| 348 | scrollToParagraph( getCurrentParagraphIndex() ); | |
| 349 | } | |
| 350 | ); | |
| 351 | } | |
| 352 | ||
| 353 | private void initScrollEventListener( final FileEditorTab tab ) { | |
| 354 | final var scrollPane = tab.getScrollPane(); | |
| 355 | final var scrollBar = getPreviewPane().getVerticalScrollBar(); | |
| 356 | ||
| 357 | addShowListener( scrollPane, ( __ ) -> { | |
| 358 | final var handler = new ScrollEventHandler( scrollPane, scrollBar ); | |
| 359 | handler.enabledProperty().bind( tab.selectedProperty() ); | |
| 360 | } ); | |
| 361 | } | |
| 362 | ||
| 363 | /** | |
| 364 | * Listen for changes to the any particular paragraph and perform a quick | |
| 365 | * spell check upon it. The style classes in the editor will be changed to | |
| 366 | * mark any spelling mistakes in the paragraph. The user may then interact | |
| 367 | * with any misspelled word (i.e., any piece of text that is marked) to | |
| 368 | * revise the spelling. | |
| 369 | * | |
| 370 | * @param tab The tab to spellcheck. | |
| 371 | */ | |
| 372 | private void initSpellCheckListener( final FileEditorTab tab ) { | |
| 373 | final var editor = tab.getEditorPane().getEditor(); | |
| 374 | ||
| 375 | // When the editor first appears, run a full spell check. This allows | |
| 376 | // spell checking while typing to be restricted to the active paragraph, | |
| 377 | // which is usually substantially smaller than the whole document. | |
| 378 | addShowListener( | |
| 379 | editor, ( __ ) -> spellcheck( editor, editor.getText() ) | |
| 380 | ); | |
| 381 | ||
| 382 | // Use the plain text changes so that notifications of style changes | |
| 383 | // are suppressed. Checking against the identity ensures that only | |
| 384 | // new text additions or deletions trigger proofreading. | |
| 385 | editor.plainTextChanges() | |
| 386 | .filter( p -> !p.isIdentity() ).subscribe( change -> { | |
| 387 | ||
| 388 | // Only perform a spell check on the current paragraph. The | |
| 389 | // entire document is processed once, when opened. | |
| 390 | final var offset = change.getPosition(); | |
| 391 | final var position = editor.offsetToPosition( offset, Forward ); | |
| 392 | final var paraId = position.getMajor(); | |
| 393 | final var paragraph = editor.getParagraph( paraId ); | |
| 394 | final var text = paragraph.getText(); | |
| 395 | ||
| 396 | // Ensure that styles aren't doubled-up. | |
| 397 | editor.clearStyle( paraId ); | |
| 398 | ||
| 399 | spellcheck( editor, text, paraId ); | |
| 400 | } ); | |
| 401 | } | |
| 402 | ||
| 403 | /** | |
| 404 | * Listen for new tab selection events. | |
| 405 | */ | |
| 406 | private void initTabChangedListener() { | |
| 407 | final FileEditorTabPane editorPane = getFileEditorPane(); | |
| 408 | ||
| 409 | // Update the preview pane changing tabs. | |
| 410 | editorPane.addTabSelectionListener( | |
| 411 | ( tabPane, oldTab, newTab ) -> { | |
| 412 | if( newTab == null ) { | |
| 413 | // Clear the preview pane when closing an editor. When the last | |
| 414 | // tab is closed, this ensures that the preview pane is empty. | |
| 415 | getPreviewPane().clear(); | |
| 416 | } | |
| 417 | else { | |
| 418 | final var tab = (FileEditorTab) newTab; | |
| 419 | updateVariableNameInjector( tab ); | |
| 420 | process( tab ); | |
| 421 | } | |
| 422 | } | |
| 423 | ); | |
| 424 | } | |
| 425 | ||
| 426 | /** | |
| 427 | * Reloads the preferences from the previous session. | |
| 428 | */ | |
| 429 | private void initPreferences() { | |
| 430 | initDefinitionPane(); | |
| 431 | getFileEditorPane().initPreferences(); | |
| 432 | getUserPreferences().addSaveEventHandler( mRPreferencesListener ); | |
| 433 | } | |
| 434 | ||
| 435 | private void initVariableNameInjector() { | |
| 436 | updateVariableNameInjector( getActiveFileEditorTab() ); | |
| 437 | } | |
| 438 | ||
| 439 | /** | |
| 440 | * Calls the listener when the given node is shown for the first time. The | |
| 441 | * visible property is not the same as the initial showing event; visibility | |
| 442 | * can be triggered numerous times (such as going off screen). | |
| 443 | * <p> | |
| 444 | * This is called, for example, before the drag handler can be attached, | |
| 445 | * because the scrollbar for the text editor pane must be visible. | |
| 446 | * </p> | |
| 447 | * | |
| 448 | * @param node The node to watch for showing. | |
| 449 | * @param consumer The consumer to invoke when the event fires. | |
| 450 | */ | |
| 451 | private void addShowListener( | |
| 452 | final Node node, final Consumer<Void> consumer ) { | |
| 453 | final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) -> | |
| 454 | runLater( () -> { | |
| 455 | if( newShow ) { | |
| 456 | try { | |
| 457 | consumer.accept( null ); | |
| 458 | } catch( final Exception ex ) { | |
| 459 | alert( ex ); | |
| 460 | } | |
| 461 | } | |
| 462 | } ); | |
| 463 | ||
| 464 | Val.flatMap( node.sceneProperty(), Scene::windowProperty ) | |
| 465 | .flatMap( Window::showingProperty ) | |
| 466 | .addListener( listener ); | |
| 467 | } | |
| 468 | ||
| 469 | private void scrollToParagraph( final int id ) { | |
| 470 | scrollToParagraph( id, false ); | |
| 471 | } | |
| 472 | ||
| 473 | /** | |
| 474 | * @param id The paragraph to scroll to, will be approximated if it doesn't | |
| 475 | * exist. | |
| 476 | * @param force {@code true} means to force scrolling immediately, which | |
| 477 | * should only be attempted when it is known that the document | |
| 478 | * has been fully rendered. Otherwise the internal map of ID | |
| 479 | * attributes will be incomplete and scrolling will flounder. | |
| 480 | */ | |
| 481 | private void scrollToParagraph( final int id, final boolean force ) { | |
| 482 | synchronized( mMutex ) { | |
| 483 | final var previewPane = getPreviewPane(); | |
| 484 | final var scrollPane = previewPane.getScrollPane(); | |
| 485 | final int approxId = getActiveEditorPane().approximateParagraphId( id ); | |
| 486 | ||
| 487 | if( force ) { | |
| 488 | previewPane.scrollTo( approxId ); | |
| 489 | } | |
| 490 | else { | |
| 491 | previewPane.tryScrollTo( approxId ); | |
| 492 | } | |
| 493 | ||
| 494 | scrollPane.repaint(); | |
| 495 | } | |
| 496 | } | |
| 497 | ||
| 498 | private void updateVariableNameInjector( final FileEditorTab tab ) { | |
| 499 | getVariableNameInjector().addListener( tab ); | |
| 500 | } | |
| 501 | ||
| 502 | /** | |
| 503 | * Called whenever the preview pane becomes out of sync with the file editor | |
| 504 | * tab. This can be called when the text changes, the caret paragraph | |
| 505 | * changes, or the file tab changes. | |
| 506 | * | |
| 507 | * @param tab The file editor tab that has been changed in some fashion. | |
| 508 | */ | |
| 509 | private void process( final FileEditorTab tab ) { | |
| 510 | if( tab != null ) { | |
| 511 | getPreviewPane().setPath( tab.getPath() ); | |
| 512 | ||
| 513 | final Processor<String> processor = getProcessors().computeIfAbsent( | |
| 514 | tab, p -> createProcessors( tab ) | |
| 515 | ); | |
| 516 | ||
| 517 | try { | |
| 518 | processChain( processor, tab.getEditorText() ); | |
| 519 | } catch( final Exception ex ) { | |
| 520 | alert( ex ); | |
| 521 | } | |
| 522 | } | |
| 523 | } | |
| 524 | ||
| 525 | /** | |
| 526 | * Executes the processing chain, operating on the given string. | |
| 527 | * | |
| 528 | * @param handler The first processor in the chain to call. | |
| 529 | * @param text The initial value of the text to process. | |
| 530 | * @return The final value of the text that was processed by the chain. | |
| 531 | */ | |
| 532 | private String processChain( Processor<String> handler, String text ) { | |
| 533 | while( handler != null && text != null ) { | |
| 534 | text = handler.apply( text ); | |
| 535 | handler = handler.next(); | |
| 536 | } | |
| 537 | ||
| 538 | return text; | |
| 539 | } | |
| 540 | ||
| 541 | private void renderActiveTab() { | |
| 542 | process( getActiveFileEditorTab() ); | |
| 543 | } | |
| 544 | ||
| 545 | /** | |
| 546 | * Called when a definition source is opened. | |
| 547 | * | |
| 548 | * @param path Path to the definition source that was opened. | |
| 549 | */ | |
| 550 | private void openDefinitions( final Path path ) { | |
| 551 | try { | |
| 552 | final var ds = createDefinitionSource( path ); | |
| 553 | setDefinitionSource( ds ); | |
| 554 | ||
| 555 | final var prefs = getUserPreferences(); | |
| 556 | prefs.definitionPathProperty().setValue( path.toFile() ); | |
| 557 | prefs.save(); | |
| 558 | ||
| 559 | final var tooltipPath = new Tooltip( path.toString() ); | |
| 560 | tooltipPath.setShowDelay( Duration.millis( 200 ) ); | |
| 561 | ||
| 562 | final var pane = getDefinitionPane(); | |
| 563 | pane.update( ds ); | |
| 564 | pane.addTreeChangeHandler( mTreeHandler ); | |
| 565 | pane.addKeyEventHandler( mDefinitionKeyHandler ); | |
| 566 | pane.filenameProperty().setValue( path.getFileName().toString() ); | |
| 567 | pane.setTooltip( tooltipPath ); | |
| 568 | ||
| 569 | interpolateResolvedMap(); | |
| 570 | } catch( final Exception ex ) { | |
| 571 | alert( ex ); | |
| 572 | } | |
| 573 | } | |
| 574 | ||
| 575 | private void exportDefinitions( final Path path ) { | |
| 576 | try { | |
| 577 | final var pane = getDefinitionPane(); | |
| 578 | final var root = pane.getTreeView().getRoot(); | |
| 579 | final var problemChild = pane.isTreeWellFormed(); | |
| 580 | ||
| 581 | if( problemChild == null ) { | |
| 582 | getDefinitionSource().getTreeAdapter().export( root, path ); | |
| 583 | } | |
| 584 | else { | |
| 585 | alert( "yaml.error.tree.form", problemChild.getValue() ); | |
| 586 | } | |
| 587 | } catch( final Exception ex ) { | |
| 588 | alert( ex ); | |
| 589 | } | |
| 590 | } | |
| 591 | ||
| 592 | private void interpolateResolvedMap() { | |
| 593 | final var treeMap = getDefinitionPane().toMap(); | |
| 594 | final var map = new HashMap<>( treeMap ); | |
| 595 | MapInterpolator.interpolate( map ); | |
| 596 | ||
| 597 | getResolvedMap().clear(); | |
| 598 | getResolvedMap().putAll( map ); | |
| 599 | } | |
| 600 | ||
| 601 | private void initDefinitionPane() { | |
| 602 | openDefinitions( getDefinitionPath() ); | |
| 603 | } | |
| 604 | ||
| 605 | //---- File actions ------------------------------------------------------- | |
| 606 | ||
| 607 | /** | |
| 608 | * Called when an {@link Observable} instance has changed. This is called | |
| 609 | * by both the {@link Snitch} service and the notify service. The @link | |
| 610 | * Snitch} service can be called for different file types, including | |
| 611 | * {@link DefinitionSource} instances. | |
| 612 | * | |
| 613 | * @param observable The observed instance. | |
| 614 | * @param value The noteworthy item. | |
| 615 | */ | |
| 616 | @Override | |
| 617 | public void update( final Observable observable, final Object value ) { | |
| 618 | if( value instanceof Path && observable instanceof Snitch ) { | |
| 619 | updateSelectedTab(); | |
| 620 | } | |
| 621 | } | |
| 622 | ||
| 623 | /** | |
| 624 | * Called when a file has been modified. | |
| 625 | */ | |
| 626 | private void updateSelectedTab() { | |
| 627 | rerender(); | |
| 628 | } | |
| 629 | ||
| 630 | /** | |
| 631 | * After resetting the processors, they will refresh anew to be up-to-date | |
| 632 | * with the files (text and definition) currently loaded into the editor. | |
| 633 | */ | |
| 634 | private void resetProcessors() { | |
| 635 | getProcessors().clear(); | |
| 636 | } | |
| 637 | ||
| 638 | //---- File actions ------------------------------------------------------- | |
| 639 | ||
| 640 | private void fileNew() { | |
| 641 | getFileEditorPane().newEditor(); | |
| 642 | } | |
| 643 | ||
| 644 | private void fileOpen() { | |
| 645 | getFileEditorPane().openFileDialog(); | |
| 646 | } | |
| 647 | ||
| 648 | private void fileClose() { | |
| 649 | getFileEditorPane().closeEditor( getActiveFileEditorTab(), true ); | |
| 650 | } | |
| 651 | ||
| 652 | /** | |
| 653 | * TODO: Upon closing, first remove the tab change listeners. (There's no | |
| 654 | * need to re-render each tab when all are being closed.) | |
| 655 | */ | |
| 656 | private void fileCloseAll() { | |
| 657 | getFileEditorPane().closeAllEditors(); | |
| 658 | } | |
| 659 | ||
| 660 | private void fileSave() { | |
| 661 | getFileEditorPane().saveEditor( getActiveFileEditorTab() ); | |
| 662 | } | |
| 663 | ||
| 664 | private void fileSaveAs() { | |
| 665 | final FileEditorTab editor = getActiveFileEditorTab(); | |
| 666 | getFileEditorPane().saveEditorAs( editor ); | |
| 667 | getProcessors().remove( editor ); | |
| 668 | ||
| 669 | try { | |
| 670 | process( editor ); | |
| 671 | } catch( final Exception ex ) { | |
| 672 | alert( ex ); | |
| 673 | } | |
| 674 | } | |
| 675 | ||
| 676 | private void fileSaveAll() { | |
| 677 | getFileEditorPane().saveAllEditors(); | |
| 678 | } | |
| 679 | ||
| 680 | private void fileExit() { | |
| 681 | final Window window = getWindow(); | |
| 682 | fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) ); | |
| 683 | } | |
| 684 | ||
| 685 | //---- Edit actions ------------------------------------------------------- | |
| 686 | ||
| 687 | /** | |
| 688 | * Transform the Markdown into HTML then copy that HTML into the copy | |
| 689 | * buffer. | |
| 690 | */ | |
| 691 | private void copyHtml() { | |
| 692 | final var markdown = getActiveEditorPane().getText(); | |
| 693 | final var processors = createProcessorFactory().createProcessors( | |
| 694 | getActiveFileEditorTab() | |
| 695 | ); | |
| 696 | ||
| 697 | final var chain = processors.remove( HtmlPreviewProcessor.class ); | |
| 698 | ||
| 699 | final String html = processChain( chain, markdown ); | |
| 700 | ||
| 701 | final Clipboard clipboard = Clipboard.getSystemClipboard(); | |
| 702 | final ClipboardContent content = new ClipboardContent(); | |
| 703 | content.putString( html ); | |
| 704 | clipboard.setContent( content ); | |
| 705 | } | |
| 706 | ||
| 707 | /** | |
| 708 | * Used to find text in the active file editor window. | |
| 709 | */ | |
| 710 | private void editFind() { | |
| 711 | final TextField input = getFindTextField(); | |
| 712 | getStatusBar().setGraphic( input ); | |
| 713 | input.requestFocus(); | |
| 714 | } | |
| 715 | ||
| 716 | public void editFindNext() { | |
| 717 | getActiveFileEditorTab().searchNext( getFindTextField().getText() ); | |
| 718 | } | |
| 719 | ||
| 720 | public void editPreferences() { | |
| 721 | getUserPreferences().show(); | |
| 722 | } | |
| 723 | ||
| 724 | //---- Insert actions ----------------------------------------------------- | |
| 725 | ||
| 726 | /** | |
| 727 | * Delegates to the active editor to handle wrapping the current text | |
| 728 | * selection with leading and trailing strings. | |
| 729 | * | |
| 730 | * @param leading The string to put before the selection. | |
| 731 | * @param trailing The string to put after the selection. | |
| 732 | */ | |
| 733 | private void insertMarkdown( | |
| 734 | final String leading, final String trailing ) { | |
| 735 | getActiveEditorPane().surroundSelection( leading, trailing ); | |
| 736 | } | |
| 737 | ||
| 738 | private void insertMarkdown( | |
| 739 | final String leading, final String trailing, final String hint ) { | |
| 740 | getActiveEditorPane().surroundSelection( leading, trailing, hint ); | |
| 741 | } | |
| 742 | ||
| 743 | //---- View actions ------------------------------------------------------- | |
| 744 | ||
| 745 | private void viewRefresh() { | |
| 746 | rerender(); | |
| 747 | } | |
| 748 | ||
| 749 | //---- Help actions ------------------------------------------------------- | |
| 750 | ||
| 751 | private void helpAbout() { | |
| 752 | final Alert alert = new Alert( AlertType.INFORMATION ); | |
| 753 | alert.setTitle( get( "Dialog.about.title" ) ); | |
| 754 | alert.setHeaderText( get( "Dialog.about.header" ) ); | |
| 755 | alert.setContentText( get( "Dialog.about.content" ) ); | |
| 756 | alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) ); | |
| 757 | alert.initOwner( getWindow() ); | |
| 758 | ||
| 759 | alert.showAndWait(); | |
| 760 | } | |
| 761 | ||
| 762 | //---- Member creators ---------------------------------------------------- | |
| 763 | ||
| 764 | private SpellChecker createSpellChecker() { | |
| 765 | try { | |
| 766 | final Collection<String> lexicon = readLexicon( "en.txt" ); | |
| 767 | return SymSpellSpeller.forLexicon( lexicon ); | |
| 768 | } catch( final Exception ex ) { | |
| 769 | alert( ex ); | |
| 770 | return new PermissiveSpeller(); | |
| 771 | } | |
| 772 | } | |
| 773 | ||
| 774 | /** | |
| 775 | * Factory to create processors that are suited to different file types. | |
| 776 | * | |
| 777 | * @param tab The tab that is subjected to processing. | |
| 778 | * @return A processor suited to the file type specified by the tab's path. | |
| 779 | */ | |
| 780 | private Processor<String> createProcessors( final FileEditorTab tab ) { | |
| 781 | return createProcessorFactory().createProcessors( tab ); | |
| 782 | } | |
| 783 | ||
| 784 | private ProcessorFactory createProcessorFactory() { | |
| 785 | return new ProcessorFactory( getPreviewPane(), getResolvedMap() ); | |
| 786 | } | |
| 787 | ||
| 788 | private HTMLPreviewPane createHTMLPreviewPane() { | |
| 789 | return new HTMLPreviewPane(); | |
| 790 | } | |
| 791 | ||
| 792 | private DefinitionSource createDefaultDefinitionSource() { | |
| 793 | return new YamlDefinitionSource( getDefinitionPath() ); | |
| 794 | } | |
| 795 | ||
| 796 | private DefinitionSource createDefinitionSource( final Path path ) { | |
| 797 | try { | |
| 798 | return createDefinitionFactory().createDefinitionSource( path ); | |
| 799 | } catch( final Exception ex ) { | |
| 800 | alert( ex ); | |
| 801 | return createDefaultDefinitionSource(); | |
| 802 | } | |
| 803 | } | |
| 804 | ||
| 805 | private TextField createFindTextField() { | |
| 806 | return new TextField(); | |
| 807 | } | |
| 808 | ||
| 809 | private DefinitionFactory createDefinitionFactory() { | |
| 810 | return new DefinitionFactory(); | |
| 811 | } | |
| 812 | ||
| 813 | private StatusBar createStatusBar() { | |
| 814 | return new StatusBar(); | |
| 815 | } | |
| 816 | ||
| 817 | private Scene createScene() { | |
| 818 | final SplitPane splitPane = new SplitPane( | |
| 819 | getDefinitionPane(), | |
| 820 | getFileEditorPane(), | |
| 821 | getPreviewPane() ); | |
| 822 | ||
| 823 | splitPane.setDividerPositions( | |
| 824 | getFloat( K_PANE_SPLIT_DEFINITION, .22f ), | |
| 825 | getFloat( K_PANE_SPLIT_EDITOR, .60f ), | |
| 826 | getFloat( K_PANE_SPLIT_PREVIEW, .18f ) ); | |
| 827 | ||
| 828 | getDefinitionPane().prefHeightProperty() | |
| 829 | .bind( splitPane.heightProperty() ); | |
| 830 | ||
| 831 | final BorderPane borderPane = new BorderPane(); | |
| 832 | borderPane.setPrefSize( 1280, 800 ); | |
| 833 | borderPane.setTop( createMenuBar() ); | |
| 834 | borderPane.setBottom( getStatusBar() ); | |
| 835 | borderPane.setCenter( splitPane ); | |
| 836 | ||
| 837 | final VBox statusBar = new VBox(); | |
| 838 | statusBar.setAlignment( Pos.BASELINE_CENTER ); | |
| 839 | statusBar.getChildren().add( getLineNumberText() ); | |
| 840 | getStatusBar().getRightItems().add( statusBar ); | |
| 841 | ||
| 842 | // Force preview pane refresh on Windows. | |
| 843 | if( SystemUtils.IS_OS_WINDOWS ) { | |
| 844 | splitPane.getDividers().get( 1 ).positionProperty().addListener( | |
| 845 | ( l, oValue, nValue ) -> runLater( | |
| 846 | () -> getPreviewPane().getScrollPane().repaint() | |
| 847 | ) | |
| 848 | ); | |
| 849 | } | |
| 850 | ||
| 851 | return new Scene( borderPane ); | |
| 852 | } | |
| 853 | ||
| 854 | private Text createLineNumberText() { | |
| 855 | return new Text( get( STATUS_BAR_LINE, 1, 1, 1 ) ); | |
| 856 | } | |
| 857 | ||
| 858 | private Node createMenuBar() { | |
| 859 | final BooleanBinding activeFileEditorIsNull = | |
| 860 | getFileEditorPane().activeFileEditorProperty().isNull(); | |
| 861 | ||
| 862 | // File actions | |
| 863 | final Action fileNewAction = new ActionBuilder() | |
| 864 | .setText( "Main.menu.file.new" ) | |
| 865 | .setAccelerator( "Shortcut+N" ) | |
| 866 | .setIcon( FILE_ALT ) | |
| 867 | .setAction( e -> fileNew() ) | |
| 868 | .build(); | |
| 869 | final Action fileOpenAction = new ActionBuilder() | |
| 870 | .setText( "Main.menu.file.open" ) | |
| 871 | .setAccelerator( "Shortcut+O" ) | |
| 872 | .setIcon( FOLDER_OPEN_ALT ) | |
| 873 | .setAction( e -> fileOpen() ) | |
| 874 | .build(); | |
| 875 | final Action fileCloseAction = new ActionBuilder() | |
| 876 | .setText( "Main.menu.file.close" ) | |
| 877 | .setAccelerator( "Shortcut+W" ) | |
| 878 | .setAction( e -> fileClose() ) | |
| 879 | .setDisable( activeFileEditorIsNull ) | |
| 880 | .build(); | |
| 881 | final Action fileCloseAllAction = new ActionBuilder() | |
| 882 | .setText( "Main.menu.file.close_all" ) | |
| 883 | .setAction( e -> fileCloseAll() ) | |
| 884 | .setDisable( activeFileEditorIsNull ) | |
| 885 | .build(); | |
| 886 | final Action fileSaveAction = new ActionBuilder() | |
| 887 | .setText( "Main.menu.file.save" ) | |
| 888 | .setAccelerator( "Shortcut+S" ) | |
| 889 | .setIcon( FLOPPY_ALT ) | |
| 890 | .setAction( e -> fileSave() ) | |
| 891 | .setDisable( createActiveBooleanProperty( | |
| 892 | FileEditorTab::modifiedProperty ).not() ) | |
| 893 | .build(); | |
| 894 | final Action fileSaveAsAction = new ActionBuilder() | |
| 895 | .setText( "Main.menu.file.save_as" ) | |
| 896 | .setAction( e -> fileSaveAs() ) | |
| 897 | .setDisable( activeFileEditorIsNull ) | |
| 898 | .build(); | |
| 899 | final Action fileSaveAllAction = new ActionBuilder() | |
| 900 | .setText( "Main.menu.file.save_all" ) | |
| 901 | .setAccelerator( "Shortcut+Shift+S" ) | |
| 902 | .setAction( e -> fileSaveAll() ) | |
| 903 | .setDisable( Bindings.not( | |
| 904 | getFileEditorPane().anyFileEditorModifiedProperty() ) ) | |
| 905 | .build(); | |
| 906 | final Action fileExitAction = new ActionBuilder() | |
| 907 | .setText( "Main.menu.file.exit" ) | |
| 908 | .setAction( e -> fileExit() ) | |
| 909 | .build(); | |
| 910 | ||
| 911 | // Edit actions | |
| 912 | final Action editCopyHtmlAction = new ActionBuilder() | |
| 913 | .setText( "Main.menu.edit.copy.html" ) | |
| 914 | .setIcon( HTML5 ) | |
| 915 | .setAction( e -> copyHtml() ) | |
| 916 | .setDisable( activeFileEditorIsNull ) | |
| 917 | .build(); | |
| 918 | ||
| 919 | final Action editUndoAction = new ActionBuilder() | |
| 920 | .setText( "Main.menu.edit.undo" ) | |
| 921 | .setAccelerator( "Shortcut+Z" ) | |
| 922 | .setIcon( UNDO ) | |
| 923 | .setAction( e -> getActiveEditorPane().undo() ) | |
| 924 | .setDisable( createActiveBooleanProperty( | |
| 925 | FileEditorTab::canUndoProperty ).not() ) | |
| 926 | .build(); | |
| 927 | final Action editRedoAction = new ActionBuilder() | |
| 928 | .setText( "Main.menu.edit.redo" ) | |
| 929 | .setAccelerator( "Shortcut+Y" ) | |
| 930 | .setIcon( REPEAT ) | |
| 931 | .setAction( e -> getActiveEditorPane().redo() ) | |
| 932 | .setDisable( createActiveBooleanProperty( | |
| 933 | FileEditorTab::canRedoProperty ).not() ) | |
| 934 | .build(); | |
| 935 | ||
| 936 | final Action editCutAction = new ActionBuilder() | |
| 937 | .setText( "Main.menu.edit.cut" ) | |
| 938 | .setAccelerator( "Shortcut+X" ) | |
| 939 | .setIcon( CUT ) | |
| 940 | .setAction( e -> getActiveEditorPane().cut() ) | |
| 941 | .setDisable( activeFileEditorIsNull ) | |
| 942 | .build(); | |
| 943 | final Action editCopyAction = new ActionBuilder() | |
| 944 | .setText( "Main.menu.edit.copy" ) | |
| 945 | .setAccelerator( "Shortcut+C" ) | |
| 946 | .setIcon( COPY ) | |
| 947 | .setAction( e -> getActiveEditorPane().copy() ) | |
| 948 | .setDisable( activeFileEditorIsNull ) | |
| 949 | .build(); | |
| 950 | final Action editPasteAction = new ActionBuilder() | |
| 951 | .setText( "Main.menu.edit.paste" ) | |
| 952 | .setAccelerator( "Shortcut+V" ) | |
| 953 | .setIcon( PASTE ) | |
| 954 | .setAction( e -> getActiveEditorPane().paste() ) | |
| 955 | .setDisable( activeFileEditorIsNull ) | |
| 956 | .build(); | |
| 957 | final Action editSelectAllAction = new ActionBuilder() | |
| 958 | .setText( "Main.menu.edit.selectAll" ) | |
| 959 | .setAccelerator( "Shortcut+A" ) | |
| 960 | .setAction( e -> getActiveEditorPane().selectAll() ) | |
| 961 | .setDisable( activeFileEditorIsNull ) | |
| 962 | .build(); | |
| 963 | ||
| 964 | final Action editFindAction = new ActionBuilder() | |
| 965 | .setText( "Main.menu.edit.find" ) | |
| 966 | .setAccelerator( "Ctrl+F" ) | |
| 967 | .setIcon( SEARCH ) | |
| 968 | .setAction( e -> editFind() ) | |
| 969 | .setDisable( activeFileEditorIsNull ) | |
| 970 | .build(); | |
| 971 | final Action editFindNextAction = new ActionBuilder() | |
| 972 | .setText( "Main.menu.edit.find.next" ) | |
| 973 | .setAccelerator( "F3" ) | |
| 974 | .setIcon( null ) | |
| 975 | .setAction( e -> editFindNext() ) | |
| 976 | .setDisable( activeFileEditorIsNull ) | |
| 977 | .build(); | |
| 978 | final Action editPreferencesAction = new ActionBuilder() | |
| 979 | .setText( "Main.menu.edit.preferences" ) | |
| 980 | .setAccelerator( "Ctrl+Alt+S" ) | |
| 981 | .setAction( e -> editPreferences() ) | |
| 982 | .build(); | |
| 983 | ||
| 984 | // Format actions | |
| 985 | final Action formatBoldAction = new ActionBuilder() | |
| 986 | .setText( "Main.menu.format.bold" ) | |
| 987 | .setAccelerator( "Shortcut+B" ) | |
| 988 | .setIcon( BOLD ) | |
| 989 | .setAction( e -> insertMarkdown( "**", "**" ) ) | |
| 990 | .setDisable( activeFileEditorIsNull ) | |
| 991 | .build(); | |
| 992 | final Action formatItalicAction = new ActionBuilder() | |
| 993 | .setText( "Main.menu.format.italic" ) | |
| 994 | .setAccelerator( "Shortcut+I" ) | |
| 995 | .setIcon( ITALIC ) | |
| 996 | .setAction( e -> insertMarkdown( "*", "*" ) ) | |
| 997 | .setDisable( activeFileEditorIsNull ) | |
| 998 | .build(); | |
| 999 | final Action formatSuperscriptAction = new ActionBuilder() | |
| 1000 | .setText( "Main.menu.format.superscript" ) | |
| 1001 | .setAccelerator( "Shortcut+[" ) | |
| 1002 | .setIcon( SUPERSCRIPT ) | |
| 1003 | .setAction( e -> insertMarkdown( "^", "^" ) ) | |
| 1004 | .setDisable( activeFileEditorIsNull ) | |
| 1005 | .build(); | |
| 1006 | final Action formatSubscriptAction = new ActionBuilder() | |
| 1007 | .setText( "Main.menu.format.subscript" ) | |
| 1008 | .setAccelerator( "Shortcut+]" ) | |
| 1009 | .setIcon( SUBSCRIPT ) | |
| 1010 | .setAction( e -> insertMarkdown( "~", "~" ) ) | |
| 1011 | .setDisable( activeFileEditorIsNull ) | |
| 1012 | .build(); | |
| 1013 | final Action formatStrikethroughAction = new ActionBuilder() | |
| 1014 | .setText( "Main.menu.format.strikethrough" ) | |
| 1015 | .setAccelerator( "Shortcut+T" ) | |
| 1016 | .setIcon( STRIKETHROUGH ) | |
| 1017 | .setAction( e -> insertMarkdown( "~~", "~~" ) ) | |
| 1018 | .setDisable( activeFileEditorIsNull ) | |
| 1019 | .build(); | |
| 1020 | ||
| 1021 | // Insert actions | |
| 1022 | final Action insertBlockquoteAction = new ActionBuilder() | |
| 1023 | .setText( "Main.menu.insert.blockquote" ) | |
| 1024 | .setAccelerator( "Ctrl+Q" ) | |
| 1025 | .setIcon( QUOTE_LEFT ) | |
| 1026 | .setAction( e -> insertMarkdown( "\n\n> ", "" ) ) | |
| 1027 | .setDisable( activeFileEditorIsNull ) | |
| 1028 | .build(); | |
| 1029 | final Action insertCodeAction = new ActionBuilder() | |
| 1030 | .setText( "Main.menu.insert.code" ) | |
| 1031 | .setAccelerator( "Shortcut+K" ) | |
| 1032 | .setIcon( CODE ) | |
| 1033 | .setAction( e -> insertMarkdown( "`", "`" ) ) | |
| 1034 | .setDisable( activeFileEditorIsNull ) | |
| 1035 | .build(); | |
| 1036 | final Action insertFencedCodeBlockAction = new ActionBuilder() | |
| 1037 | .setText( "Main.menu.insert.fenced_code_block" ) | |
| 1038 | .setAccelerator( "Shortcut+Shift+K" ) | |
| 1039 | .setIcon( FILE_CODE_ALT ) | |
| 1040 | .setAction( e -> insertMarkdown( | |
| 1041 | "\n\n```\n", | |
| 1042 | "\n```\n\n", | |
| 1043 | get( "Main.menu.insert.fenced_code_block.prompt" ) ) ) | |
| 1044 | .setDisable( activeFileEditorIsNull ) | |
| 1045 | .build(); | |
| 1046 | final Action insertLinkAction = new ActionBuilder() | |
| 1047 | .setText( "Main.menu.insert.link" ) | |
| 1048 | .setAccelerator( "Shortcut+L" ) | |
| 1049 | .setIcon( LINK ) | |
| 1050 | .setAction( e -> getActiveEditorPane().insertLink() ) | |
| 1051 | .setDisable( activeFileEditorIsNull ) | |
| 1052 | .build(); | |
| 1053 | final Action insertImageAction = new ActionBuilder() | |
| 1054 | .setText( "Main.menu.insert.image" ) | |
| 1055 | .setAccelerator( "Shortcut+G" ) | |
| 1056 | .setIcon( PICTURE_ALT ) | |
| 1057 | .setAction( e -> getActiveEditorPane().insertImage() ) | |
| 1058 | .setDisable( activeFileEditorIsNull ) | |
| 1059 | .build(); | |
| 1060 | ||
| 1061 | // Number of heading actions (H1 ... H3) | |
| 1062 | final int HEADINGS = 3; | |
| 1063 | final Action[] headings = new Action[ HEADINGS ]; | |
| 1064 | ||
| 1065 | for( int i = 1; i <= HEADINGS; i++ ) { | |
| 1066 | final String hashes = new String( new char[ i ] ).replace( "\0", "#" ); | |
| 1067 | final String markup = String.format( "%n%n%s ", hashes ); | |
| 1068 | final String text = "Main.menu.insert.heading." + i; | |
| 1069 | final String accelerator = "Shortcut+" + i; | |
| 1070 | final String prompt = text + ".prompt"; | |
| 1071 | ||
| 1072 | headings[ i - 1 ] = new ActionBuilder() | |
| 1073 | .setText( text ) | |
| 1074 | .setAccelerator( accelerator ) | |
| 1075 | .setIcon( HEADER ) | |
| 1076 | .setAction( e -> insertMarkdown( markup, "", get( prompt ) ) ) | |
| 1077 | .setDisable( activeFileEditorIsNull ) | |
| 1078 | .build(); | |
| 1079 | } | |
| 1080 | ||
| 1081 | final Action insertUnorderedListAction = new ActionBuilder() | |
| 1082 | .setText( "Main.menu.insert.unordered_list" ) | |
| 1083 | .setAccelerator( "Shortcut+U" ) | |
| 1084 | .setIcon( LIST_UL ) | |
| 1085 | .setAction( e -> insertMarkdown( "\n\n* ", "" ) ) | |
| 1086 | .setDisable( activeFileEditorIsNull ) | |
| 1087 | .build(); | |
| 1088 | final Action insertOrderedListAction = new ActionBuilder() | |
| 1089 | .setText( "Main.menu.insert.ordered_list" ) | |
| 1090 | .setAccelerator( "Shortcut+Shift+O" ) | |
| 1091 | .setIcon( LIST_OL ) | |
| 1092 | .setAction( e -> insertMarkdown( | |
| 1093 | "\n\n1. ", "" ) ) | |
| 1094 | .setDisable( activeFileEditorIsNull ) | |
| 1095 | .build(); | |
| 1096 | final Action insertHorizontalRuleAction = new ActionBuilder() | |
| 1097 | .setText( "Main.menu.insert.horizontal_rule" ) | |
| 1098 | .setAccelerator( "Shortcut+H" ) | |
| 1099 | .setAction( e -> insertMarkdown( | |
| 1100 | "\n\n---\n\n", "" ) ) | |
| 1101 | .setDisable( activeFileEditorIsNull ) | |
| 1102 | .build(); | |
| 1103 | ||
| 1104 | // Definition actions | |
| 1105 | final Action definitionCreateAction = new ActionBuilder() | |
| 1106 | .setText( "Main.menu.definition.create" ) | |
| 1107 | .setIcon( TREE ) | |
| 1108 | .setAction( e -> getDefinitionPane().addItem() ) | |
| 1109 | .build(); | |
| 1110 | final Action definitionInsertAction = new ActionBuilder() | |
| 1111 | .setText( "Main.menu.definition.insert" ) | |
| 1112 | .setAccelerator( "Ctrl+Space" ) | |
| 1113 | .setIcon( STAR ) | |
| 1114 | .setAction( e -> definitionInsert() ) | |
| 1115 | .build(); | |
| 1116 | ||
| 1117 | // Help actions | |
| 1118 | final Action helpAboutAction = new ActionBuilder() | |
| 1119 | .setText( "Main.menu.help.about" ) | |
| 1120 | .setAction( e -> helpAbout() ) | |
| 1121 | .build(); | |
| 1122 | ||
| 1123 | //---- MenuBar ---- | |
| 1124 | ||
| 1125 | // File Menu | |
| 1126 | final var fileMenu = ActionUtils.createMenu( | |
| 1127 | get( "Main.menu.file" ), | |
| 1128 | fileNewAction, | |
| 1129 | fileOpenAction, | |
| 1130 | null, | |
| 1131 | fileCloseAction, | |
| 1132 | fileCloseAllAction, | |
| 1133 | null, | |
| 1134 | fileSaveAction, | |
| 1135 | fileSaveAsAction, | |
| 1136 | fileSaveAllAction, | |
| 1137 | null, | |
| 1138 | fileExitAction ); | |
| 1139 | ||
| 1140 | // Edit Menu | |
| 1141 | final var editMenu = ActionUtils.createMenu( | |
| 1142 | get( "Main.menu.edit" ), | |
| 1143 | editCopyHtmlAction, | |
| 1144 | null, | |
| 1145 | editUndoAction, | |
| 1146 | editRedoAction, | |
| 1147 | null, | |
| 1148 | editCutAction, | |
| 1149 | editCopyAction, | |
| 1150 | editPasteAction, | |
| 1151 | editSelectAllAction, | |
| 1152 | null, | |
| 1153 | editFindAction, | |
| 1154 | editFindNextAction, | |
| 1155 | null, | |
| 1156 | editPreferencesAction ); | |
| 1157 | ||
| 1158 | // Format Menu | |
| 1159 | final var formatMenu = ActionUtils.createMenu( | |
| 1160 | get( "Main.menu.format" ), | |
| 1161 | formatBoldAction, | |
| 1162 | formatItalicAction, | |
| 1163 | formatSuperscriptAction, | |
| 1164 | formatSubscriptAction, | |
| 1165 | formatStrikethroughAction | |
| 1166 | ); | |
| 1167 | ||
| 1168 | // Insert Menu | |
| 1169 | final var insertMenu = ActionUtils.createMenu( | |
| 1170 | get( "Main.menu.insert" ), | |
| 1171 | insertBlockquoteAction, | |
| 1172 | insertCodeAction, | |
| 1173 | insertFencedCodeBlockAction, | |
| 1174 | null, | |
| 1175 | insertLinkAction, | |
| 1176 | insertImageAction, | |
| 1177 | null, | |
| 1178 | headings[ 0 ], | |
| 1179 | headings[ 1 ], | |
| 1180 | headings[ 2 ], | |
| 1181 | null, | |
| 1182 | insertUnorderedListAction, | |
| 1183 | insertOrderedListAction, | |
| 1184 | insertHorizontalRuleAction | |
| 1185 | ); | |
| 1186 | ||
| 1187 | // Definition Menu | |
| 1188 | final var definitionMenu = ActionUtils.createMenu( | |
| 1189 | get( "Main.menu.definition" ), | |
| 1190 | definitionCreateAction, | |
| 1191 | definitionInsertAction ); | |
| 1192 | ||
| 1193 | // Help Menu | |
| 1194 | final var helpMenu = ActionUtils.createMenu( | |
| 1195 | get( "Main.menu.help" ), | |
| 1196 | helpAboutAction ); | |
| 1197 | ||
| 1198 | //---- MenuBar ---- | |
| 1199 | final var menuBar = new MenuBar( | |
| 1200 | fileMenu, | |
| 1201 | editMenu, | |
| 1202 | formatMenu, | |
| 1203 | insertMenu, | |
| 1204 | definitionMenu, | |
| 1205 | helpMenu ); | |
| 1206 | ||
| 1207 | //---- ToolBar ---- | |
| 1208 | final var toolBar = ActionUtils.createToolBar( | |
| 1209 | fileNewAction, | |
| 1210 | fileOpenAction, | |
| 1211 | fileSaveAction, | |
| 1212 | null, | |
| 1213 | editUndoAction, | |
| 1214 | editRedoAction, | |
| 1215 | editCutAction, | |
| 1216 | editCopyAction, | |
| 1217 | editPasteAction, | |
| 1218 | null, | |
| 1219 | formatBoldAction, | |
| 1220 | formatItalicAction, | |
| 1221 | formatSuperscriptAction, | |
| 1222 | formatSubscriptAction, | |
| 1223 | insertBlockquoteAction, | |
| 1224 | insertCodeAction, | |
| 1225 | insertFencedCodeBlockAction, | |
| 1226 | null, | |
| 1227 | insertLinkAction, | |
| 1228 | insertImageAction, | |
| 1229 | null, | |
| 1230 | headings[ 0 ], | |
| 1231 | null, | |
| 1232 | insertUnorderedListAction, | |
| 1233 | insertOrderedListAction ); | |
| 1234 | ||
| 1235 | return new VBox( menuBar, toolBar ); | |
| 1236 | } | |
| 1237 | ||
| 1238 | /** | |
| 1239 | * Performs the autoinsert function on the active file editor. | |
| 1240 | */ | |
| 1241 | private void definitionInsert() { | |
| 1242 | } | |
| 1243 | ||
| 1244 | /** | |
| 1245 | * Creates a boolean property that is bound to another boolean value of the | |
| 1246 | * active editor. | |
| 1247 | */ | |
| 1248 | private BooleanProperty createActiveBooleanProperty( | |
| 1249 | final Function<FileEditorTab, ObservableBooleanValue> func ) { | |
| 1250 | ||
| 1251 | final BooleanProperty b = new SimpleBooleanProperty(); | |
| 1252 | final FileEditorTab tab = getActiveFileEditorTab(); | |
| 1253 | ||
| 1254 | if( tab != null ) { | |
| 1255 | b.bind( func.apply( tab ) ); | |
| 1256 | } | |
| 1257 | ||
| 1258 | getFileEditorPane().activeFileEditorProperty().addListener( | |
| 1259 | ( observable, oldFileEditor, newFileEditor ) -> { | |
| 1260 | b.unbind(); | |
| 1261 | ||
| 1262 | if( newFileEditor == null ) { | |
| 1263 | b.set( false ); | |
| 1264 | } | |
| 1265 | else { | |
| 1266 | b.bind( func.apply( newFileEditor ) ); | |
| 1267 | } | |
| 1268 | } | |
| 1269 | ); | |
| 1270 | ||
| 1271 | return b; | |
| 1272 | } | |
| 1273 | ||
| 1274 | //---- Convenience accessors ---------------------------------------------- | |
| 1275 | ||
| 1276 | private Preferences getPreferences() { | |
| 1277 | return sOptions.getState(); | |
| 1278 | } | |
| 1279 | ||
| 1280 | private int getCurrentParagraphIndex() { | |
| 1281 | return getActiveEditorPane().getCurrentParagraphIndex(); | |
| 1282 | } | |
| 1283 | ||
| 1284 | private float getFloat( final String key, final float defaultValue ) { | |
| 1285 | return getPreferences().getFloat( key, defaultValue ); | |
| 1286 | } | |
| 1287 | ||
| 1288 | public Window getWindow() { | |
| 1289 | return getScene().getWindow(); | |
| 1290 | } | |
| 1291 | ||
| 1292 | private MarkdownEditorPane getActiveEditorPane() { | |
| 1293 | return getActiveFileEditorTab().getEditorPane(); | |
| 1294 | } | |
| 1295 | ||
| 1296 | private FileEditorTab getActiveFileEditorTab() { | |
| 1297 | return getFileEditorPane().getActiveFileEditor(); | |
| 1298 | } | |
| 1299 | ||
| 1300 | //---- Member accessors --------------------------------------------------- | |
| 1301 | ||
| 1302 | protected Scene getScene() { | |
| 1303 | return mScene; | |
| 1304 | } | |
| 1305 | ||
| 1306 | private SpellChecker getSpellChecker() { | |
| 1307 | return mSpellChecker; | |
| 1308 | } | |
| 1309 | ||
| 1310 | private Map<FileEditorTab, Processor<String>> getProcessors() { | |
| 1311 | return mProcessors; | |
| 1312 | } | |
| 1313 | ||
| 1314 | private FileEditorTabPane getFileEditorPane() { | |
| 1315 | return mFileEditorPane; | |
| 1316 | } | |
| 1317 | ||
| 1318 | private HTMLPreviewPane getPreviewPane() { | |
| 1319 | return mPreviewPane; | |
| 1320 | } | |
| 1321 | ||
| 1322 | private void setDefinitionSource( | |
| 1323 | final DefinitionSource definitionSource ) { | |
| 1324 | assert definitionSource != null; | |
| 1325 | mDefinitionSource = definitionSource; | |
| 1326 | } | |
| 1327 | ||
| 1328 | private DefinitionSource getDefinitionSource() { | |
| 1329 | return mDefinitionSource; | |
| 1330 | } | |
| 1331 | ||
| 1332 | private DefinitionPane getDefinitionPane() { | |
| 1333 | return mDefinitionPane; | |
| 1334 | } | |
| 1335 | ||
| 1336 | private Text getLineNumberText() { | |
| 1337 | return mLineNumberText; | |
| 1338 | } | |
| 1339 | ||
| 1340 | private StatusBar getStatusBar() { | |
| 1341 | return mStatusBar; | |
| 1342 | } | |
| 1343 | ||
| 1344 | private TextField getFindTextField() { | |
| 1345 | return mFindTextField; | |
| 1346 | } | |
| 1347 | ||
| 1348 | private DefinitionNameInjector getVariableNameInjector() { | |
| 1349 | return mVariableNameInjector; | |
| 1350 | } | |
| 1351 | ||
| 1352 | /** | |
| 1353 | * Returns the variable map of interpolated definitions. | |
| 1354 | * | |
| 1355 | * @return A map to help dereference variables. | |
| 1356 | */ | |
| 1357 | private Map<String, String> getResolvedMap() { | |
| 1358 | return mResolvedMap; | |
| 1359 | } | |
| 1360 | ||
| 1361 | //---- Persistence accessors ---------------------------------------------- | |
| 1362 | ||
| 1363 | private UserPreferences getUserPreferences() { | |
| 1364 | return sOptions.getUserPreferences(); | |
| 1365 | } | |
| 1366 | ||
| 1367 | private Path getDefinitionPath() { | |
| 1368 | return getUserPreferences().getDefinitionPath(); | |
| 1369 | } | |
| 1370 | ||
| 1371 | //---- Spelling ----------------------------------------------------------- | |
| 1372 | ||
| 1373 | /** | |
| 1374 | * Delegates to {@link #spellcheck(StyleClassedTextArea, String, int)}. | |
| 1375 | * This is called to spell check the document, rather than a single paragraph. | |
| 1376 | * | |
| 1377 | * @param text The full document text. | |
| 1378 | */ | |
| 1379 | private void spellcheck( | |
| 1380 | final StyleClassedTextArea editor, final String text ) { | |
| 1381 | spellcheck( editor, text, -1 ); | |
| 1382 | } | |
| 1383 | ||
| 1384 | /** | |
| 1385 | * Spellchecks a subset of the entire document. | |
| 1386 | * | |
| 1387 | * @param text Look up words for this text in the lexicon. | |
| 1388 | * @param paraId Set to -1 to apply resulting style spans to the entire | |
| 1389 | * text. | |
| 1390 | */ | |
| 1391 | private void spellcheck( | |
| 1392 | final StyleClassedTextArea editor, final String text, final int paraId ) { | |
| 1393 | final var builder = new StyleSpansBuilder<Collection<String>>(); | |
| 1394 | final var runningIndex = new AtomicInteger( 0 ); | |
| 1395 | final var checker = getSpellChecker(); | |
| 1396 | ||
| 1397 | // The text nodes must be relayed through a contextual "visitor" that | |
| 1398 | // can return text in chunks with correlative offsets into the string. | |
| 1399 | // This allows Markdown, R Markdown, XML, and R XML documents to return | |
| 1400 | // sets of words to check. | |
| 1401 | ||
| 1402 | final var node = mParser.parse( text ); | |
| 1403 | final var visitor = new TextVisitor( ( visited, bIndex, eIndex ) -> { | |
| 1404 | // Treat hyphenated compound words as individual words. | |
| 1405 | final var check = visited.replace( '-', ' ' ); | |
| 1406 | ||
| 1407 | checker.proofread( check, ( misspelled, prevIndex, currIndex ) -> { | |
| 1408 | prevIndex += bIndex; | |
| 1409 | currIndex += bIndex; | |
| 1410 | ||
| 1411 | // Clear styling between lexiconically absent words. | |
| 1412 | builder.add( emptyList(), prevIndex - runningIndex.get() ); | |
| 1413 | builder.add( singleton( "spelling" ), currIndex - prevIndex ); | |
| 1414 | runningIndex.set( currIndex ); | |
| 1415 | } ); | |
| 1416 | } ); | |
| 1417 | ||
| 1418 | visitor.visit( node ); | |
| 1419 | ||
| 1420 | // If the running index was set, at least one word triggered the listener. | |
| 1421 | if( runningIndex.get() > 0 ) { | |
| 1422 | // Clear styling after the last lexiconically absent word. | |
| 1423 | builder.add( emptyList(), text.length() - runningIndex.get() ); | |
| 1424 | ||
| 1425 | final var spans = builder.create(); | |
| 1426 | ||
| 1427 | if( paraId >= 0 ) { | |
| 1428 | editor.setStyleSpans( paraId, 0, spans ); | |
| 1429 | } | |
| 1430 | else { | |
| 1431 | editor.setStyleSpans( 0, spans ); | |
| 1432 | } | |
| 1433 | } | |
| 1434 | } | |
| 1435 | ||
| 1436 | @SuppressWarnings("SameParameterValue") | |
| 1437 | private Collection<String> readLexicon( final String filename ) | |
| 1438 | throws Exception { | |
| 1439 | final var path = Paths.get( LEXICONS_DIRECTORY, filename ).toString(); | |
| 1440 | final var classLoader = MainWindow.class.getClassLoader(); | |
| 1441 | ||
| 1442 | try( final var resource = classLoader.getResourceAsStream( path ) ) { | |
| 1443 | assert resource != null; | |
| 1444 | ||
| 1445 | return new BufferedReader( new InputStreamReader( resource, UTF_8 ) ) | |
| 1446 | .lines() | |
| 1447 | .collect( Collectors.toList() ); | |
| 1448 | } | |
| 1449 | } | |
| 1450 | ||
| 1451 | // TODO: Replace using Markdown processor instantiated for Markdown files. | |
| 1452 | // FIXME: https://github.com/DaveJarvis/scrivenvar/issues/59 | |
| 1453 | private final Parser mParser = Parser.builder().build(); | |
| 1454 | ||
| 1455 | // TODO: Replace with generic interface; provide Markdown/XML implementations. | |
| 1456 | // FIXME: https://github.com/DaveJarvis/scrivenvar/issues/59 | |
| 1457 | private static final class TextVisitor { | |
| 1458 | private final NodeVisitor mVisitor = new NodeVisitor( new VisitHandler<>( | |
| 1459 | com.vladsch.flexmark.ast.Text.class, this::visit ) | |
| 1460 | ); | |
| 1461 | ||
| 1462 | private final SpellCheckListener mConsumer; | |
| 1463 | ||
| 1464 | public TextVisitor( final SpellCheckListener consumer ) { | |
| 1465 | mConsumer = consumer; | |
| 1466 | } | |
| 1467 | ||
| 1468 | private void visit( final com.vladsch.flexmark.util.ast.Node node ) { | |
| 1469 | if( node instanceof com.vladsch.flexmark.ast.Text ) { | |
| 1470 | mConsumer.accept( node.getChars().toString(), | |
| 1471 | node.getStartOffset(), | |
| 1472 | node.getEndOffset() ); | |
| 1473 | } | |
| 1474 | ||
| 1475 | mVisitor.visitChildren( node ); | |
| 1476 | } | |
| 1477 | } | |
| 1478 | } | |
| 1 | 1479 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * * Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * * Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar; | |
| 28 | ||
| 29 | import java.text.MessageFormat; | |
| 30 | import java.util.ResourceBundle; | |
| 31 | import java.util.Stack; | |
| 32 | ||
| 33 | import static com.scrivenvar.Constants.APP_BUNDLE_NAME; | |
| 34 | import static java.util.ResourceBundle.getBundle; | |
| 35 | ||
| 36 | /** | |
| 37 | * Recursively resolves message properties. Property values can refer to other | |
| 38 | * properties using a <code>${var}</code> syntax. | |
| 39 | */ | |
| 40 | public class Messages { | |
| 41 | ||
| 42 | private static final ResourceBundle RESOURCE_BUNDLE = | |
| 43 | getBundle( APP_BUNDLE_NAME ); | |
| 44 | ||
| 45 | private Messages() { | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * Return the value of a resource bundle value after having resolved any | |
| 50 | * references to other bundle variables. | |
| 51 | * | |
| 52 | * @param props The bundle containing resolvable properties. | |
| 53 | * @param s The value for a key to resolve. | |
| 54 | * @return The value of the key with all references recursively dereferenced. | |
| 55 | */ | |
| 56 | @SuppressWarnings("SameParameterValue") | |
| 57 | private static String resolve( final ResourceBundle props, final String s ) { | |
| 58 | final int len = s.length(); | |
| 59 | final Stack<StringBuilder> stack = new Stack<>(); | |
| 60 | ||
| 61 | StringBuilder sb = new StringBuilder( 256 ); | |
| 62 | boolean open = false; | |
| 63 | ||
| 64 | for( int i = 0; i < len; i++ ) { | |
| 65 | final char c = s.charAt( i ); | |
| 66 | ||
| 67 | switch( c ) { | |
| 68 | case '$': { | |
| 69 | if( i + 1 < len && s.charAt( i + 1 ) == '{' ) { | |
| 70 | stack.push( sb ); | |
| 71 | sb = new StringBuilder( 256 ); | |
| 72 | i++; | |
| 73 | open = true; | |
| 74 | } | |
| 75 | ||
| 76 | break; | |
| 77 | } | |
| 78 | ||
| 79 | case '}': { | |
| 80 | if( open ) { | |
| 81 | open = false; | |
| 82 | final String name = sb.toString(); | |
| 83 | ||
| 84 | sb = stack.pop(); | |
| 85 | sb.append( props.getString( name ) ); | |
| 86 | break; | |
| 87 | } | |
| 88 | } | |
| 89 | ||
| 90 | default: { | |
| 91 | sb.append( c ); | |
| 92 | break; | |
| 93 | } | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | if( open ) { | |
| 98 | throw new IllegalArgumentException( "missing '}'" ); | |
| 99 | } | |
| 100 | ||
| 101 | return sb.toString(); | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * Returns the value for a key from the message bundle. | |
| 106 | * | |
| 107 | * @param key Retrieve the value for this key. | |
| 108 | * @return The value for the key. | |
| 109 | */ | |
| 110 | public static String get( final String key ) { | |
| 111 | try { | |
| 112 | return resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) ); | |
| 113 | } catch( final Exception ex ) { | |
| 114 | return key; | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | public static String getLiteral( final String key ) { | |
| 119 | return RESOURCE_BUNDLE.getString( key ); | |
| 120 | } | |
| 121 | ||
| 122 | public static String get( final String key, final boolean interpolate ) { | |
| 123 | return interpolate ? get( key ) : getLiteral( key ); | |
| 124 | } | |
| 125 | ||
| 126 | /** | |
| 127 | * Returns the value for a key from the message bundle with the arguments | |
| 128 | * replacing <code>{#}</code> place holders. | |
| 129 | * | |
| 130 | * @param key Retrieve the value for this key. | |
| 131 | * @param args The values to substitute for place holders. | |
| 132 | * @return The value for the key. | |
| 133 | */ | |
| 134 | public static String get( final String key, final Object... args ) { | |
| 135 | return MessageFormat.format( get( key ), args ); | |
| 136 | } | |
| 137 | } | |
| 1 | 138 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import javafx.beans.property.BooleanProperty; | |
| 31 | import javafx.beans.property.SimpleBooleanProperty; | |
| 32 | import javafx.event.Event; | |
| 33 | import javafx.event.EventHandler; | |
| 34 | import javafx.scene.Node; | |
| 35 | import javafx.scene.control.ScrollBar; | |
| 36 | import javafx.scene.control.skin.ScrollBarSkin; | |
| 37 | import javafx.scene.input.MouseEvent; | |
| 38 | import javafx.scene.input.ScrollEvent; | |
| 39 | import javafx.scene.layout.StackPane; | |
| 40 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 41 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 42 | ||
| 43 | import javax.swing.*; | |
| 44 | ||
| 45 | import static javafx.geometry.Orientation.VERTICAL; | |
| 46 | ||
| 47 | /** | |
| 48 | * Converts scroll events from {@link VirtualizedScrollPane} scroll bars to | |
| 49 | * an instance of {@link JScrollBar}. | |
| 50 | * <p> | |
| 51 | * Called to synchronize the scrolling areas for either scrolling with the | |
| 52 | * mouse or scrolling using the scrollbar's thumb. Both are required to avoid | |
| 53 | * scrolling on the estimatedScrollYProperty that occurs when text events | |
| 54 | * fire. Scrolling performed for text events are handled separately to ensure | |
| 55 | * the preview panel scrolls to the same position in the Markdown editor, | |
| 56 | * taking into account things like images, tables, and other potentially | |
| 57 | * long vertical presentation items. | |
| 58 | * </p> | |
| 59 | */ | |
| 60 | public final class ScrollEventHandler implements EventHandler<Event> { | |
| 61 | ||
| 62 | private final class MouseHandler implements EventHandler<MouseEvent> { | |
| 63 | private final EventHandler<? super MouseEvent> mOldHandler; | |
| 64 | ||
| 65 | /** | |
| 66 | * Constructs a new handler for mouse scrolling events. | |
| 67 | * | |
| 68 | * @param oldHandler Receives the event after scrolling takes place. | |
| 69 | */ | |
| 70 | private MouseHandler( final EventHandler<? super MouseEvent> oldHandler ) { | |
| 71 | mOldHandler = oldHandler; | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public void handle( final MouseEvent event ) { | |
| 76 | ScrollEventHandler.this.handle( event ); | |
| 77 | mOldHandler.handle( event ); | |
| 78 | } | |
| 79 | } | |
| 80 | ||
| 81 | private final class ScrollHandler implements EventHandler<ScrollEvent> { | |
| 82 | @Override | |
| 83 | public void handle( final ScrollEvent event ) { | |
| 84 | ScrollEventHandler.this.handle( event ); | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | private final VirtualizedScrollPane<StyleClassedTextArea> mEditorScrollPane; | |
| 89 | private final JScrollBar mPreviewScrollBar; | |
| 90 | private final BooleanProperty mEnabled = new SimpleBooleanProperty(); | |
| 91 | ||
| 92 | /** | |
| 93 | * @param editorScrollPane Scroll event source (human movement). | |
| 94 | * @param previewScrollBar Scroll event destination (corresponding movement). | |
| 95 | */ | |
| 96 | public ScrollEventHandler( | |
| 97 | final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane, | |
| 98 | final JScrollBar previewScrollBar ) { | |
| 99 | mEditorScrollPane = editorScrollPane; | |
| 100 | mPreviewScrollBar = previewScrollBar; | |
| 101 | ||
| 102 | mEditorScrollPane.addEventFilter( ScrollEvent.ANY, new ScrollHandler() ); | |
| 103 | ||
| 104 | final var thumb = getVerticalScrollBarThumb( mEditorScrollPane ); | |
| 105 | thumb.setOnMouseDragged( new MouseHandler( thumb.getOnMouseDragged() ) ); | |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Gets a property intended to be bound to selected property of the tab being | |
| 110 | * scrolled. This is required because there's only one preview pane but | |
| 111 | * multiple editor panes. Each editor pane maintains its own scroll position. | |
| 112 | * | |
| 113 | * @return A {@link BooleanProperty} representing whether the scroll | |
| 114 | * events for this tab are to be executed. | |
| 115 | */ | |
| 116 | public BooleanProperty enabledProperty() { | |
| 117 | return mEnabled; | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Scrolls the preview scrollbar relative to the edit scrollbar. Algorithm | |
| 122 | * is based on Karl Tauber's ratio calculation. | |
| 123 | * | |
| 124 | * @param event Unused; either {@link MouseEvent} or {@link ScrollEvent} | |
| 125 | */ | |
| 126 | @Override | |
| 127 | public void handle( final Event event ) { | |
| 128 | if( isEnabled() ) { | |
| 129 | final var eScrollPane = getEditorScrollPane(); | |
| 130 | final int eScrollY = | |
| 131 | eScrollPane.estimatedScrollYProperty().getValue().intValue(); | |
| 132 | final int eHeight = (int) | |
| 133 | (eScrollPane.totalHeightEstimateProperty().getValue().intValue() | |
| 134 | - eScrollPane.getHeight()); | |
| 135 | final double eRatio = eHeight > 0 | |
| 136 | ? Math.min( Math.max( eScrollY / (float) eHeight, 0 ), 1 ) : 0; | |
| 137 | ||
| 138 | final var pScrollBar = getPreviewScrollBar(); | |
| 139 | final var pHeight = pScrollBar.getMaximum() - pScrollBar.getHeight(); | |
| 140 | final var pScrollY = (int) (pHeight * eRatio); | |
| 141 | ||
| 142 | pScrollBar.setValue( pScrollY ); | |
| 143 | pScrollBar.getParent().repaint(); | |
| 144 | } | |
| 145 | } | |
| 146 | ||
| 147 | private StackPane getVerticalScrollBarThumb( | |
| 148 | final VirtualizedScrollPane<StyleClassedTextArea> pane ) { | |
| 149 | final ScrollBar scrollBar = getVerticalScrollBar( pane ); | |
| 150 | final ScrollBarSkin skin = (ScrollBarSkin) (scrollBar.skinProperty().get()); | |
| 151 | ||
| 152 | for( final Node node : skin.getChildren() ) { | |
| 153 | // Brittle, but what can you do? | |
| 154 | if( node.getStyleClass().contains( "thumb" ) ) { | |
| 155 | return (StackPane) node; | |
| 156 | } | |
| 157 | } | |
| 158 | ||
| 159 | throw new IllegalArgumentException( "No scroll bar skin found." ); | |
| 160 | } | |
| 161 | ||
| 162 | private ScrollBar getVerticalScrollBar( | |
| 163 | final VirtualizedScrollPane<StyleClassedTextArea> pane ) { | |
| 164 | ||
| 165 | for( final Node node : pane.getChildrenUnmodifiable() ) { | |
| 166 | if( node instanceof ScrollBar ) { | |
| 167 | final ScrollBar scrollBar = (ScrollBar) node; | |
| 168 | ||
| 169 | if( scrollBar.getOrientation() == VERTICAL ) { | |
| 170 | return scrollBar; | |
| 171 | } | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | throw new IllegalArgumentException( "No vertical scroll pane found." ); | |
| 176 | } | |
| 177 | ||
| 178 | private boolean isEnabled() { | |
| 179 | // TODO: As a minor optimization, when this is set to false, it could remove | |
| 180 | // the MouseHandler and ScrollHandler so that events only dispatch to one | |
| 181 | // object (instead of one per editor tab). | |
| 182 | return mEnabled.get(); | |
| 183 | } | |
| 184 | ||
| 185 | private VirtualizedScrollPane<StyleClassedTextArea> getEditorScrollPane() { | |
| 186 | return mEditorScrollPane; | |
| 187 | } | |
| 188 | ||
| 189 | private JScrollBar getPreviewScrollBar() { | |
| 190 | return mPreviewScrollBar; | |
| 191 | } | |
| 192 | } | |
| 1 | 193 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import java.util.HashMap; | |
| 31 | import java.util.Map; | |
| 32 | import java.util.ServiceLoader; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for loading services. The services are treated as singleton | |
| 36 | * instances. | |
| 37 | */ | |
| 38 | public class Services { | |
| 39 | ||
| 40 | @SuppressWarnings("rawtypes") | |
| 41 | private static final Map<Class, Object> SINGLETONS = new HashMap<>(); | |
| 42 | ||
| 43 | /** | |
| 44 | * Loads a service based on its interface definition. This will return an | |
| 45 | * existing instance if the class has already been instantiated. | |
| 46 | * | |
| 47 | * @param <T> The service to load. | |
| 48 | * @param api The interface definition for the service. | |
| 49 | * @return A class that implements the interface. | |
| 50 | */ | |
| 51 | @SuppressWarnings("unchecked") | |
| 52 | public static <T> T load( final Class<T> api ) { | |
| 53 | final T o = (T) get( api ); | |
| 54 | ||
| 55 | return o == null ? newInstance( api ) : o; | |
| 56 | } | |
| 57 | ||
| 58 | private static <T> T newInstance( final Class<T> api ) { | |
| 59 | final ServiceLoader<T> services = ServiceLoader.load( api ); | |
| 60 | ||
| 61 | for( final T service : services ) { | |
| 62 | if( service != null ) { | |
| 63 | // Re-use the same instance the next time the class is loaded. | |
| 64 | put( api, service ); | |
| 65 | return service; | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | throw new RuntimeException( "No implementation for: " + api ); | |
| 70 | } | |
| 71 | ||
| 72 | @SuppressWarnings("rawtypes") | |
| 73 | private static void put( final Class key, Object value ) { | |
| 74 | SINGLETONS.put( key, value ); | |
| 75 | } | |
| 76 | ||
| 77 | @SuppressWarnings("rawtypes") | |
| 78 | private static Object get( final Class api ) { | |
| 79 | return SINGLETONS.get( api ); | |
| 80 | } | |
| 81 | } | |
| 1 | 82 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar; | |
| 29 | ||
| 30 | import com.scrivenvar.service.events.Notifier; | |
| 31 | import org.controlsfx.control.StatusBar; | |
| 32 | ||
| 33 | import static com.scrivenvar.Constants.STATUS_BAR_OK; | |
| 34 | import static com.scrivenvar.Messages.get; | |
| 35 | import static javafx.application.Platform.runLater; | |
| 36 | ||
| 37 | /** | |
| 38 | * Responsible for passing notifications about exceptions (or other error | |
| 39 | * messages) through the application. Once the Event Bus is implemented, this | |
| 40 | * class can go away. | |
| 41 | */ | |
| 42 | public class StatusBarNotifier { | |
| 43 | private static final String OK = get( STATUS_BAR_OK, "OK" ); | |
| 44 | ||
| 45 | private static final Notifier sNotifier = Services.load( Notifier.class ); | |
| 46 | private static StatusBar sStatusBar; | |
| 47 | ||
| 48 | public static void setStatusBar( final StatusBar statusBar ) { | |
| 49 | sStatusBar = statusBar; | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Resets the status bar to a default message. | |
| 54 | */ | |
| 55 | public static void clearAlert() { | |
| 56 | // Don't burden the repaint thread if there's no status bar change. | |
| 57 | if( !OK.equals( sStatusBar.getText() ) ) { | |
| 58 | update( OK ); | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Updates the status bar with a custom message. | |
| 64 | * | |
| 65 | * @param key The resource bundle key associated with a message (typically | |
| 66 | * to inform the user about an error). | |
| 67 | */ | |
| 68 | public static void alert( final String key ) { | |
| 69 | update( get( key ) ); | |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * Updates the status bar with a custom message. | |
| 74 | * | |
| 75 | * @param key The property key having a value to populate with arguments. | |
| 76 | * @param args The placeholder values to substitute into the key's value. | |
| 77 | */ | |
| 78 | public static void alert( final String key, final Object... args ) { | |
| 79 | update( get( key, args ) ); | |
| 80 | } | |
| 81 | ||
| 82 | /** | |
| 83 | * Called when an exception occurs that warrants the user's attention. | |
| 84 | * | |
| 85 | * @param ex The exception with a message that the user should know about. | |
| 86 | */ | |
| 87 | public static void alert( final Exception ex ) { | |
| 88 | update( ex.getMessage() ); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Updates the status bar to show the given message. | |
| 93 | * | |
| 94 | * @param s The message to show in the status bar. | |
| 95 | */ | |
| 96 | private static void update( final String s ) { | |
| 97 | runLater( | |
| 98 | () -> { | |
| 99 | final var i = s.indexOf( '\n' ); | |
| 100 | sStatusBar.setText( s.substring( 0, i > 0 ? i : s.length() ) ); | |
| 101 | } | |
| 102 | ); | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Returns the global {@link Notifier} instance that can be used for opening | |
| 107 | * pop-up alert messages. | |
| 108 | * | |
| 109 | * @return The pop-up {@link Notifier} dispatcher. | |
| 110 | */ | |
| 111 | public static Notifier getNotifier() { | |
| 112 | return sNotifier; | |
| 113 | } | |
| 114 | } | |
| 1 | 115 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.adapters; | |
| 29 | ||
| 30 | import org.xhtmlrenderer.event.DocumentListener; | |
| 31 | ||
| 32 | /** | |
| 33 | * Allows subclasses to implement specific events. | |
| 34 | */ | |
| 35 | public class DocumentAdapter implements DocumentListener { | |
| 36 | @Override | |
| 37 | public void documentStarted() { | |
| 38 | } | |
| 39 | ||
| 40 | @Override | |
| 41 | public void documentLoaded() { | |
| 42 | } | |
| 43 | ||
| 44 | @Override | |
| 45 | public void onLayoutException( final Throwable t ) { | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public void onRenderException( final Throwable t ) { | |
| 50 | } | |
| 51 | } | |
| 1 | 52 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.adapters; | |
| 29 | ||
| 30 | import org.w3c.dom.Element; | |
| 31 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 32 | import org.xhtmlrenderer.simple.extend.FormSubmissionListener; | |
| 33 | ||
| 34 | public abstract class ReplacedElementAdapter implements ReplacedElementFactory { | |
| 35 | @Override | |
| 36 | public void reset() { | |
| 37 | } | |
| 38 | ||
| 39 | @Override | |
| 40 | public void remove( final Element e ) { | |
| 41 | } | |
| 42 | ||
| 43 | @Override | |
| 44 | public void setFormSubmissionListener( | |
| 45 | final FormSubmissionListener listener ) { | |
| 46 | } | |
| 47 | } | |
| 1 | 48 |
| 1 | /* | |
| 2 | * Copyright 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import com.scrivenvar.Messages; | |
| 31 | import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | |
| 32 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 33 | import javafx.beans.property.ObjectProperty; | |
| 34 | import javafx.beans.property.SimpleObjectProperty; | |
| 35 | import javafx.event.ActionEvent; | |
| 36 | import javafx.scene.control.Button; | |
| 37 | import javafx.scene.control.Tooltip; | |
| 38 | import javafx.scene.input.KeyCode; | |
| 39 | import javafx.scene.input.KeyEvent; | |
| 40 | import javafx.stage.FileChooser; | |
| 41 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 42 | ||
| 43 | import java.io.File; | |
| 44 | import java.nio.file.Path; | |
| 45 | import java.util.ArrayList; | |
| 46 | import java.util.List; | |
| 47 | ||
| 48 | /** | |
| 49 | * Button that opens a file chooser to select a local file for a URL. | |
| 50 | */ | |
| 51 | public class BrowseFileButton extends Button { | |
| 52 | private final List<ExtensionFilter> extensionFilters = new ArrayList<>(); | |
| 53 | ||
| 54 | public BrowseFileButton() { | |
| 55 | setGraphic( | |
| 56 | FontAwesomeIconFactory.get().createIcon( FontAwesomeIcon.FILE_ALT ) | |
| 57 | ); | |
| 58 | setTooltip( new Tooltip( Messages.get( "BrowseFileButton.tooltip" ) ) ); | |
| 59 | setOnAction( this::browse ); | |
| 60 | ||
| 61 | disableProperty().bind( basePath.isNull() ); | |
| 62 | ||
| 63 | // workaround for a JavaFX bug: | |
| 64 | // avoid closing the dialog that contains this control when the user | |
| 65 | // closes the FileChooser or DirectoryChooser using the ESC key | |
| 66 | addEventHandler( KeyEvent.KEY_RELEASED, e -> { | |
| 67 | if( e.getCode() == KeyCode.ESCAPE ) { | |
| 68 | e.consume(); | |
| 69 | } | |
| 70 | } ); | |
| 71 | } | |
| 72 | ||
| 73 | public void addExtensionFilter( ExtensionFilter extensionFilter ) { | |
| 74 | extensionFilters.add( extensionFilter ); | |
| 75 | } | |
| 76 | ||
| 77 | // 'basePath' property | |
| 78 | private final ObjectProperty<Path> basePath = new SimpleObjectProperty<>(); | |
| 79 | ||
| 80 | public Path getBasePath() { | |
| 81 | return basePath.get(); | |
| 82 | } | |
| 83 | ||
| 84 | public void setBasePath( Path basePath ) { | |
| 85 | this.basePath.set( basePath ); | |
| 86 | } | |
| 87 | ||
| 88 | // 'url' property | |
| 89 | private final ObjectProperty<String> url = new SimpleObjectProperty<>(); | |
| 90 | ||
| 91 | public ObjectProperty<String> urlProperty() { | |
| 92 | return url; | |
| 93 | } | |
| 94 | ||
| 95 | protected void browse( ActionEvent e ) { | |
| 96 | FileChooser fileChooser = new FileChooser(); | |
| 97 | fileChooser.setTitle( Messages.get( "BrowseFileButton.chooser.title" ) ); | |
| 98 | fileChooser.getExtensionFilters().addAll( extensionFilters ); | |
| 99 | fileChooser.getExtensionFilters() | |
| 100 | .add( new ExtensionFilter( Messages.get( | |
| 101 | "BrowseFileButton.chooser.allFilesFilter" ), "*.*" ) ); | |
| 102 | fileChooser.setInitialDirectory( getInitialDirectory() ); | |
| 103 | File result = fileChooser.showOpenDialog( getScene().getWindow() ); | |
| 104 | if( result != null ) { | |
| 105 | updateUrl( result ); | |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | protected File getInitialDirectory() { | |
| 110 | //TODO build initial directory based on current value of 'url' property | |
| 111 | return getBasePath().toFile(); | |
| 112 | } | |
| 113 | ||
| 114 | protected void updateUrl( File file ) { | |
| 115 | String newUrl; | |
| 116 | try { | |
| 117 | newUrl = getBasePath().relativize( file.toPath() ).toString(); | |
| 118 | } catch( IllegalArgumentException ex ) { | |
| 119 | newUrl = file.toString(); | |
| 120 | } | |
| 121 | url.set( newUrl.replace( '\\', '/' ) ); | |
| 122 | } | |
| 123 | } | |
| 1 | 124 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.scrivenvar.controls; | |
| 29 | ||
| 30 | import javafx.beans.property.SimpleStringProperty; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | import javafx.scene.control.TextField; | |
| 33 | import javafx.util.StringConverter; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for escaping/unescaping characters for markdown. | |
| 37 | */ | |
| 38 | public class EscapeTextField extends TextField { | |
| 39 | ||
| 40 | public EscapeTextField() { | |
| 41 | escapedText.bindBidirectional( | |
| 42 | textProperty(), | |
| 43 | new StringConverter<>() { | |
| 44 | @Override | |
| 45 | public String toString( String object ) { | |
| 46 | return escape( object ); | |
| 47 | } | |
| 48 | ||
| 49 | @Override | |
| 50 | public String fromString( String string ) { | |
| 51 | return unescape( string ); | |
| 52 | } | |
| 53 | } | |
| 54 | ); | |
| 55 | escapeCharacters.addListener( | |
| 56 | e -> escapedText.set( escape( textProperty().get() ) ) | |
| 57 | ); | |
| 58 | } | |
| 59 | ||
| 60 | // 'escapedText' property | |
| 61 | private final StringProperty escapedText = new SimpleStringProperty(); | |
| 62 | ||
| 63 | public StringProperty escapedTextProperty() { | |
| 64 | return escapedText; | |
| 65 | } | |
| 66 | ||
| 67 | // 'escapeCharacters' property | |
| 68 | private final StringProperty escapeCharacters = new SimpleStringProperty(); | |
| 69 | ||
| 70 | public String getEscapeCharacters() { | |
| 71 | return escapeCharacters.get(); | |
| 72 | } | |
| 73 | ||
| 74 | public void setEscapeCharacters( String escapeCharacters ) { | |
| 75 | this.escapeCharacters.set( escapeCharacters ); | |
| 76 | } | |
| 77 | ||
| 78 | private String escape( final String s ) { | |
| 79 | final String escapeChars = getEscapeCharacters(); | |
| 80 | ||
| 81 | return isEmpty( escapeChars ) ? s : | |
| 82 | s.replaceAll( "([" + escapeChars.replaceAll( | |
| 83 | "(.)", | |
| 84 | "\\\\$1" ) + "])", "\\\\$1" ); | |
| 85 | } | |
| 86 | ||
| 87 | private String unescape( final String s ) { | |
| 88 | final String escapeChars = getEscapeCharacters(); | |
| 89 | ||
| 90 | return isEmpty( escapeChars ) ? s : | |
| 91 | s.replaceAll( "\\\\([" + escapeChars | |
| 92 | .replaceAll( "(.)", "\\\\$1" ) + "])", "$1" ); | |
| 93 | } | |
| 94 | ||
| 95 | private static boolean isEmpty( final String s ) { | |
| 96 | return s == null || s.isEmpty(); | |
| 97 | } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractFileFactory; | |
| 31 | import com.scrivenvar.FileType; | |
| 32 | import com.scrivenvar.definition.yaml.YamlDefinitionSource; | |
| 33 | ||
| 34 | import java.nio.file.Path; | |
| 35 | ||
| 36 | import static com.scrivenvar.Constants.GLOB_PREFIX_DEFINITION; | |
| 37 | import static com.scrivenvar.FileType.YAML; | |
| 38 | import static com.scrivenvar.util.ProtocolResolver.getProtocol; | |
| 39 | ||
| 40 | /** | |
| 41 | * Responsible for creating objects that can read and write definition data | |
| 42 | * sources. The data source could be YAML, TOML, JSON, flat files, or from a | |
| 43 | * database. | |
| 44 | */ | |
| 45 | public class DefinitionFactory extends AbstractFileFactory { | |
| 46 | ||
| 47 | /** | |
| 48 | * Default (empty) constructor. | |
| 49 | */ | |
| 50 | public DefinitionFactory() { | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Creates a definition source capable of reading definitions from the given | |
| 55 | * path. | |
| 56 | * | |
| 57 | * @param path Path to a resource containing definitions. | |
| 58 | * @return The definition source appropriate for the given path. | |
| 59 | */ | |
| 60 | public DefinitionSource createDefinitionSource( final Path path ) { | |
| 61 | assert path != null; | |
| 62 | ||
| 63 | final var protocol = getProtocol( path.toString() ); | |
| 64 | DefinitionSource result = null; | |
| 65 | ||
| 66 | if( protocol.isFile() ) { | |
| 67 | final FileType filetype = lookup( path, GLOB_PREFIX_DEFINITION ); | |
| 68 | result = createFileDefinitionSource( filetype, path ); | |
| 69 | } | |
| 70 | else { | |
| 71 | unknownFileType( protocol, path.toString() ); | |
| 72 | } | |
| 73 | ||
| 74 | return result; | |
| 75 | } | |
| 76 | ||
| 77 | /** | |
| 78 | * Creates a definition source based on the file type. | |
| 79 | * | |
| 80 | * @param filetype Property key name suffix from settings.properties file. | |
| 81 | * @param path Path to the file that corresponds to the extension. | |
| 82 | * @return A DefinitionSource capable of parsing the data stored at the path. | |
| 83 | */ | |
| 84 | private DefinitionSource createFileDefinitionSource( | |
| 85 | final FileType filetype, final Path path ) { | |
| 86 | assert filetype != null; | |
| 87 | assert path != null; | |
| 88 | ||
| 89 | if( filetype == YAML ) { | |
| 90 | return new YamlDefinitionSource( path ); | |
| 91 | } | |
| 92 | ||
| 93 | throw new IllegalArgumentException( filetype.toString() ); | |
| 94 | } | |
| 95 | } | |
| 1 | 96 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon; | |
| 31 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 32 | import javafx.beans.property.SimpleStringProperty; | |
| 33 | import javafx.beans.property.StringProperty; | |
| 34 | import javafx.collections.ObservableList; | |
| 35 | import javafx.event.ActionEvent; | |
| 36 | import javafx.event.Event; | |
| 37 | import javafx.event.EventHandler; | |
| 38 | import javafx.geometry.Insets; | |
| 39 | import javafx.geometry.Pos; | |
| 40 | import javafx.scene.Node; | |
| 41 | import javafx.scene.control.*; | |
| 42 | import javafx.scene.input.KeyEvent; | |
| 43 | import javafx.scene.layout.BorderPane; | |
| 44 | import javafx.scene.layout.HBox; | |
| 45 | import javafx.util.StringConverter; | |
| 46 | ||
| 47 | import java.util.*; | |
| 48 | ||
| 49 | import static com.scrivenvar.Messages.get; | |
| 50 | import static de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon.*; | |
| 51 | import static javafx.geometry.Pos.CENTER; | |
| 52 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 53 | ||
| 54 | /** | |
| 55 | * Provides the user interface that holds a {@link TreeView}, which | |
| 56 | * allows users to interact with key/value pairs loaded from the | |
| 57 | * {@link DocumentParser} and adapted using a {@link TreeAdapter}. | |
| 58 | */ | |
| 59 | public final class DefinitionPane extends BorderPane { | |
| 60 | ||
| 61 | /** | |
| 62 | * Contains a view of the definitions. | |
| 63 | */ | |
| 64 | private final TreeView<String> mTreeView = new TreeView<>(); | |
| 65 | ||
| 66 | /** | |
| 67 | * Handlers for key press events. | |
| 68 | */ | |
| 69 | private final Set<EventHandler<? super KeyEvent>> mKeyEventHandlers | |
| 70 | = new HashSet<>(); | |
| 71 | ||
| 72 | /** | |
| 73 | * Definition file name shown in the title of the pane. | |
| 74 | */ | |
| 75 | private final StringProperty mFilename = new SimpleStringProperty(); | |
| 76 | ||
| 77 | private final TitledPane mTitledPane = new TitledPane(); | |
| 78 | ||
| 79 | /** | |
| 80 | * Constructs a definition pane with a given tree view root. | |
| 81 | */ | |
| 82 | public DefinitionPane() { | |
| 83 | final var treeView = getTreeView(); | |
| 84 | treeView.setEditable( true ); | |
| 85 | treeView.setCellFactory( cell -> createTreeCell() ); | |
| 86 | treeView.setContextMenu( createContextMenu() ); | |
| 87 | treeView.addEventFilter( KEY_PRESSED, this::keyEventFilter ); | |
| 88 | treeView.setShowRoot( false ); | |
| 89 | getSelectionModel().setSelectionMode( SelectionMode.MULTIPLE ); | |
| 90 | ||
| 91 | final var bCreate = createButton( | |
| 92 | "create", TREE, e -> addItem() ); | |
| 93 | final var bRename = createButton( | |
| 94 | "rename", EDIT, e -> editSelectedItem() ); | |
| 95 | final var bDelete = createButton( | |
| 96 | "delete", TRASH, e -> deleteSelectedItems() ); | |
| 97 | ||
| 98 | final var buttonBar = new HBox(); | |
| 99 | buttonBar.getChildren().addAll( bCreate, bRename, bDelete ); | |
| 100 | buttonBar.setAlignment( CENTER ); | |
| 101 | buttonBar.setSpacing( 10 ); | |
| 102 | ||
| 103 | final var titledPane = getTitledPane(); | |
| 104 | titledPane.textProperty().bind( mFilename ); | |
| 105 | titledPane.setContent( treeView ); | |
| 106 | titledPane.setCollapsible( false ); | |
| 107 | titledPane.setPadding( new Insets( 0, 0, 0, 0 ) ); | |
| 108 | ||
| 109 | setTop( buttonBar ); | |
| 110 | setCenter( titledPane ); | |
| 111 | setAlignment( buttonBar, Pos.TOP_CENTER ); | |
| 112 | setAlignment( titledPane, Pos.TOP_CENTER ); | |
| 113 | ||
| 114 | titledPane.prefHeightProperty().bind( this.heightProperty() ); | |
| 115 | } | |
| 116 | ||
| 117 | public void setTooltip( final Tooltip tooltip ) { | |
| 118 | getTitledPane().setTooltip( tooltip ); | |
| 119 | } | |
| 120 | ||
| 121 | private TitledPane getTitledPane() { | |
| 122 | return mTitledPane; | |
| 123 | } | |
| 124 | ||
| 125 | private Button createButton( | |
| 126 | final String msgKey, | |
| 127 | final FontAwesomeIcon icon, | |
| 128 | final EventHandler<ActionEvent> eventHandler ) { | |
| 129 | final var keyPrefix = "Pane.definition.button." + msgKey; | |
| 130 | final var button = new Button( get( keyPrefix + ".label" ) ); | |
| 131 | button.setOnAction( eventHandler ); | |
| 132 | ||
| 133 | button.setGraphic( | |
| 134 | FontAwesomeIconFactory.get().createIcon( icon ) | |
| 135 | ); | |
| 136 | button.setTooltip( new Tooltip( get( keyPrefix + ".tooltip" ) ) ); | |
| 137 | ||
| 138 | return button; | |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * Changes the root of the {@link TreeView} to the root of the | |
| 143 | * {@link TreeView} from the {@link DefinitionSource}. | |
| 144 | * | |
| 145 | * @param definitionSource Container for the hierarchy of key/value pairs | |
| 146 | * to replace the existing hierarchy. | |
| 147 | */ | |
| 148 | public void update( final DefinitionSource definitionSource ) { | |
| 149 | assert definitionSource != null; | |
| 150 | ||
| 151 | final TreeAdapter treeAdapter = definitionSource.getTreeAdapter(); | |
| 152 | final TreeItem<String> root = treeAdapter.adapt( | |
| 153 | get( "Pane.definition.node.root.title" ) | |
| 154 | ); | |
| 155 | ||
| 156 | getTreeView().setRoot( root ); | |
| 157 | } | |
| 158 | ||
| 159 | public Map<String, String> toMap() { | |
| 160 | return TreeItemAdapter.toMap( getTreeView().getRoot() ); | |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Informs the caller of whenever any {@link TreeItem} in the {@link TreeView} | |
| 165 | * is modified. The modifications include: item value changes, item additions, | |
| 166 | * and item removals. | |
| 167 | * <p> | |
| 168 | * Safe to call multiple times; if a handler is already registered, the | |
| 169 | * old handler is used. | |
| 170 | * </p> | |
| 171 | * | |
| 172 | * @param handler The handler to call whenever any {@link TreeItem} changes. | |
| 173 | */ | |
| 174 | public void addTreeChangeHandler( | |
| 175 | final EventHandler<TreeItem.TreeModificationEvent<Event>> handler ) { | |
| 176 | final TreeItem<String> root = getTreeView().getRoot(); | |
| 177 | root.addEventHandler( TreeItem.valueChangedEvent(), handler ); | |
| 178 | root.addEventHandler( TreeItem.childrenModificationEvent(), handler ); | |
| 179 | } | |
| 180 | ||
| 181 | public void addKeyEventHandler( | |
| 182 | final EventHandler<? super KeyEvent> handler ) { | |
| 183 | getKeyEventHandlers().add( handler ); | |
| 184 | } | |
| 185 | ||
| 186 | /** | |
| 187 | * Answers whether the {@link TreeItem}s in the {@link TreeView} are suitably | |
| 188 | * well-formed for export. A tree is considered well-formed if the following | |
| 189 | * conditions are met: | |
| 190 | * | |
| 191 | * <ul> | |
| 192 | * <li>The root node contains at least one child node having a leaf.</li> | |
| 193 | * <li>There are no leaf nodes with sibling leaf nodes.</li> | |
| 194 | * </ul> | |
| 195 | * | |
| 196 | * @return {@code null} if the document is well-formed, otherwise the | |
| 197 | * problematic child {@link TreeItem}. | |
| 198 | */ | |
| 199 | public TreeItem<String> isTreeWellFormed() { | |
| 200 | final var root = getTreeView().getRoot(); | |
| 201 | ||
| 202 | for( final var child : root.getChildren() ) { | |
| 203 | final var problemChild = isWellFormed( child ); | |
| 204 | ||
| 205 | if( child.isLeaf() || problemChild != null ) { | |
| 206 | return problemChild; | |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | return null; | |
| 211 | } | |
| 212 | ||
| 213 | /** | |
| 214 | * Determines whether the document is well-formed by ensuring that | |
| 215 | * child branches do not contain multiple leaves. | |
| 216 | * | |
| 217 | * @param item The sub-tree to check for well-formedness. | |
| 218 | * @return {@code null} when the tree is well-formed, otherwise the | |
| 219 | * problematic {@link TreeItem}. | |
| 220 | */ | |
| 221 | private TreeItem<String> isWellFormed( final TreeItem<String> item ) { | |
| 222 | int childLeafs = 0; | |
| 223 | int childBranches = 0; | |
| 224 | ||
| 225 | for( final TreeItem<String> child : item.getChildren() ) { | |
| 226 | if( child.isLeaf() ) { | |
| 227 | childLeafs++; | |
| 228 | } | |
| 229 | else { | |
| 230 | childBranches++; | |
| 231 | } | |
| 232 | ||
| 233 | final var problemChild = isWellFormed( child ); | |
| 234 | ||
| 235 | if( problemChild != null ) { | |
| 236 | return problemChild; | |
| 237 | } | |
| 238 | } | |
| 239 | ||
| 240 | return ((childBranches > 0 && childLeafs == 0) || | |
| 241 | (childBranches == 0 && childLeafs <= 1)) ? null : item; | |
| 242 | } | |
| 243 | ||
| 244 | /** | |
| 245 | * Delegates to {@link DefinitionTreeItem#findLeafExact(String)}. | |
| 246 | * | |
| 247 | * @param text The value to find, never {@code null}. | |
| 248 | * @return The leaf that contains the given value, or {@code null} if | |
| 249 | * not found. | |
| 250 | */ | |
| 251 | public DefinitionTreeItem<String> findLeafExact( final String text ) { | |
| 252 | return getTreeRoot().findLeafExact( text ); | |
| 253 | } | |
| 254 | ||
| 255 | /** | |
| 256 | * Delegates to {@link DefinitionTreeItem#findLeafContains(String)}. | |
| 257 | * | |
| 258 | * @param text The value to find, never {@code null}. | |
| 259 | * @return The leaf that contains the given value, or {@code null} if | |
| 260 | * not found. | |
| 261 | */ | |
| 262 | public DefinitionTreeItem<String> findLeafContains( final String text ) { | |
| 263 | return getTreeRoot().findLeafContains( text ); | |
| 264 | } | |
| 265 | ||
| 266 | /** | |
| 267 | * Delegates to {@link DefinitionTreeItem#findLeafContains(String)}. | |
| 268 | * | |
| 269 | * @param text The value to find, never {@code null}. | |
| 270 | * @return The leaf that contains the given value, or {@code null} if | |
| 271 | * not found. | |
| 272 | */ | |
| 273 | public DefinitionTreeItem<String> findLeafContainsNoCase( | |
| 274 | final String text ) { | |
| 275 | return getTreeRoot().findLeafContainsNoCase( text ); | |
| 276 | } | |
| 277 | ||
| 278 | /** | |
| 279 | * Delegates to {@link DefinitionTreeItem#findLeafStartsWith(String)}. | |
| 280 | * | |
| 281 | * @param text The value to find, never {@code null}. | |
| 282 | * @return The leaf that contains the given value, or {@code null} if | |
| 283 | * not found. | |
| 284 | */ | |
| 285 | public DefinitionTreeItem<String> findLeafStartsWith( final String text ) { | |
| 286 | return getTreeRoot().findLeafStartsWith( text ); | |
| 287 | } | |
| 288 | ||
| 289 | /** | |
| 290 | * Expands the node to the root, recursively. | |
| 291 | * | |
| 292 | * @param <T> The type of tree item to expand (usually String). | |
| 293 | * @param node The node to expand. | |
| 294 | */ | |
| 295 | public <T> void expand( final TreeItem<T> node ) { | |
| 296 | if( node != null ) { | |
| 297 | expand( node.getParent() ); | |
| 298 | ||
| 299 | if( !node.isLeaf() ) { | |
| 300 | node.setExpanded( true ); | |
| 301 | } | |
| 302 | } | |
| 303 | } | |
| 304 | ||
| 305 | public void select( final TreeItem<String> item ) { | |
| 306 | getSelectionModel().clearSelection(); | |
| 307 | getSelectionModel().select( getTreeView().getRow( item ) ); | |
| 308 | } | |
| 309 | ||
| 310 | /** | |
| 311 | * Collapses the tree, recursively. | |
| 312 | */ | |
| 313 | public void collapse() { | |
| 314 | collapse( getTreeRoot().getChildren() ); | |
| 315 | } | |
| 316 | ||
| 317 | /** | |
| 318 | * Collapses the tree, recursively. | |
| 319 | * | |
| 320 | * @param <T> The type of tree item to expand (usually String). | |
| 321 | * @param nodes The nodes to collapse. | |
| 322 | */ | |
| 323 | private <T> void collapse( final ObservableList<TreeItem<T>> nodes ) { | |
| 324 | for( final var node : nodes ) { | |
| 325 | node.setExpanded( false ); | |
| 326 | collapse( node.getChildren() ); | |
| 327 | } | |
| 328 | } | |
| 329 | ||
| 330 | /** | |
| 331 | * @return {@code true} when the user is editing a {@link TreeItem}. | |
| 332 | */ | |
| 333 | private boolean isEditingTreeItem() { | |
| 334 | return getTreeView().editingItemProperty().getValue() != null; | |
| 335 | } | |
| 336 | ||
| 337 | /** | |
| 338 | * Changes to edit mode for the selected item. | |
| 339 | */ | |
| 340 | private void editSelectedItem() { | |
| 341 | getTreeView().edit( getSelectedItem() ); | |
| 342 | } | |
| 343 | ||
| 344 | /** | |
| 345 | * Removes all selected items from the {@link TreeView}. | |
| 346 | */ | |
| 347 | private void deleteSelectedItems() { | |
| 348 | for( final var item : getSelectedItems() ) { | |
| 349 | final var parent = item.getParent(); | |
| 350 | ||
| 351 | if( parent != null ) { | |
| 352 | parent.getChildren().remove( item ); | |
| 353 | } | |
| 354 | } | |
| 355 | } | |
| 356 | ||
| 357 | /** | |
| 358 | * Deletes the selected item. | |
| 359 | */ | |
| 360 | private void deleteSelectedItem() { | |
| 361 | final var c = getSelectedItem(); | |
| 362 | getSiblings( c ).remove( c ); | |
| 363 | } | |
| 364 | ||
| 365 | /** | |
| 366 | * Adds a new item under the selected item (or root if nothing is selected). | |
| 367 | * There are a few conditions to consider: when adding to the root, | |
| 368 | * when adding to a leaf, and when adding to a non-leaf. Items added to the | |
| 369 | * root must contain two items: a key and a value. | |
| 370 | */ | |
| 371 | public void addItem() { | |
| 372 | final var value = createTreeItem(); | |
| 373 | getSelectedItem().getChildren().add( value ); | |
| 374 | expand( value ); | |
| 375 | select( value ); | |
| 376 | } | |
| 377 | ||
| 378 | private ContextMenu createContextMenu() { | |
| 379 | final ContextMenu menu = new ContextMenu(); | |
| 380 | final ObservableList<MenuItem> items = menu.getItems(); | |
| 381 | ||
| 382 | addMenuItem( items, "Definition.menu.create" ) | |
| 383 | .setOnAction( e -> addItem() ); | |
| 384 | ||
| 385 | addMenuItem( items, "Definition.menu.rename" ) | |
| 386 | .setOnAction( e -> editSelectedItem() ); | |
| 387 | ||
| 388 | addMenuItem( items, "Definition.menu.remove" ) | |
| 389 | .setOnAction( e -> deleteSelectedItem() ); | |
| 390 | ||
| 391 | return menu; | |
| 392 | } | |
| 393 | ||
| 394 | /** | |
| 395 | * Executes hot-keys for edits to the definition tree. | |
| 396 | * | |
| 397 | * @param event Contains the key code of the key that was pressed. | |
| 398 | */ | |
| 399 | private void keyEventFilter( final KeyEvent event ) { | |
| 400 | if( !isEditingTreeItem() ) { | |
| 401 | switch( event.getCode() ) { | |
| 402 | case ENTER: | |
| 403 | expand( getSelectedItem() ); | |
| 404 | event.consume(); | |
| 405 | break; | |
| 406 | ||
| 407 | case DELETE: | |
| 408 | deleteSelectedItems(); | |
| 409 | break; | |
| 410 | ||
| 411 | case INSERT: | |
| 412 | addItem(); | |
| 413 | break; | |
| 414 | ||
| 415 | case R: | |
| 416 | if( event.isControlDown() ) { | |
| 417 | editSelectedItem(); | |
| 418 | } | |
| 419 | ||
| 420 | break; | |
| 421 | } | |
| 422 | ||
| 423 | for( final var handler : getKeyEventHandlers() ) { | |
| 424 | handler.handle( event ); | |
| 425 | } | |
| 426 | } | |
| 427 | } | |
| 428 | ||
| 429 | /** | |
| 430 | * Adds a menu item to a list of menu items. | |
| 431 | * | |
| 432 | * @param items The list of menu items to append to. | |
| 433 | * @param labelKey The resource bundle key name for the menu item's label. | |
| 434 | * @return The menu item added to the list of menu items. | |
| 435 | */ | |
| 436 | private MenuItem addMenuItem( | |
| 437 | final List<MenuItem> items, final String labelKey ) { | |
| 438 | final MenuItem menuItem = createMenuItem( labelKey ); | |
| 439 | items.add( menuItem ); | |
| 440 | return menuItem; | |
| 441 | } | |
| 442 | ||
| 443 | private MenuItem createMenuItem( final String labelKey ) { | |
| 444 | return new MenuItem( get( labelKey ) ); | |
| 445 | } | |
| 446 | ||
| 447 | private DefinitionTreeItem<String> createTreeItem() { | |
| 448 | return new DefinitionTreeItem<>( get( "Definition.menu.add.default" ) ); | |
| 449 | } | |
| 450 | ||
| 451 | private TreeCell<String> createTreeCell() { | |
| 452 | return new FocusAwareTextFieldTreeCell( createStringConverter() ) { | |
| 453 | @Override | |
| 454 | public void commitEdit( final String newValue ) { | |
| 455 | super.commitEdit( newValue ); | |
| 456 | select( getTreeItem() ); | |
| 457 | requestFocus(); | |
| 458 | } | |
| 459 | }; | |
| 460 | } | |
| 461 | ||
| 462 | @Override | |
| 463 | public void requestFocus() { | |
| 464 | super.requestFocus(); | |
| 465 | getTreeView().requestFocus(); | |
| 466 | } | |
| 467 | ||
| 468 | private StringConverter<String> createStringConverter() { | |
| 469 | return new StringConverter<>() { | |
| 470 | @Override | |
| 471 | public String toString( final String object ) { | |
| 472 | return object == null ? "" : object; | |
| 473 | } | |
| 474 | ||
| 475 | @Override | |
| 476 | public String fromString( final String string ) { | |
| 477 | return string == null ? "" : string; | |
| 478 | } | |
| 479 | }; | |
| 480 | } | |
| 481 | ||
| 482 | /** | |
| 483 | * Returns the tree view that contains the definition hierarchy. | |
| 484 | * | |
| 485 | * @return A non-null instance. | |
| 486 | */ | |
| 487 | public TreeView<String> getTreeView() { | |
| 488 | return mTreeView; | |
| 489 | } | |
| 490 | ||
| 491 | /** | |
| 492 | * Returns this pane. | |
| 493 | * | |
| 494 | * @return this | |
| 495 | */ | |
| 496 | public Node getNode() { | |
| 497 | return this; | |
| 498 | } | |
| 499 | ||
| 500 | /** | |
| 501 | * Returns the property used to set the title of the pane: the file name. | |
| 502 | * | |
| 503 | * @return A non-null property used for showing the definition file name. | |
| 504 | */ | |
| 505 | public StringProperty filenameProperty() { | |
| 506 | return mFilename; | |
| 507 | } | |
| 508 | ||
| 509 | /** | |
| 510 | * Returns the root of the tree. | |
| 511 | * | |
| 512 | * @return The first node added to the definition tree. | |
| 513 | */ | |
| 514 | private DefinitionTreeItem<String> getTreeRoot() { | |
| 515 | final var root = getTreeView().getRoot(); | |
| 516 | ||
| 517 | return root instanceof DefinitionTreeItem | |
| 518 | ? (DefinitionTreeItem<String>) root | |
| 519 | : new DefinitionTreeItem<>( "root" ); | |
| 520 | } | |
| 521 | ||
| 522 | private ObservableList<TreeItem<String>> getSiblings( | |
| 523 | final TreeItem<String> item ) { | |
| 524 | final var root = getTreeView().getRoot(); | |
| 525 | final var parent = (item == null || item == root) ? root : item.getParent(); | |
| 526 | ||
| 527 | return parent.getChildren(); | |
| 528 | } | |
| 529 | ||
| 530 | private MultipleSelectionModel<TreeItem<String>> getSelectionModel() { | |
| 531 | return getTreeView().getSelectionModel(); | |
| 532 | } | |
| 533 | ||
| 534 | /** | |
| 535 | * Returns a copy of all the selected items. | |
| 536 | * | |
| 537 | * @return A list, possibly empty, containing all selected items in the | |
| 538 | * {@link TreeView}. | |
| 539 | */ | |
| 540 | private List<TreeItem<String>> getSelectedItems() { | |
| 541 | return new ArrayList<>( getSelectionModel().getSelectedItems() ); | |
| 542 | } | |
| 543 | ||
| 544 | public TreeItem<String> getSelectedItem() { | |
| 545 | final var item = getSelectionModel().getSelectedItem(); | |
| 546 | return item == null ? getTreeView().getRoot() : item; | |
| 547 | } | |
| 548 | ||
| 549 | private Set<EventHandler<? super KeyEvent>> getKeyEventHandlers() { | |
| 550 | return mKeyEventHandlers; | |
| 551 | } | |
| 552 | ||
| 553 | /** | |
| 554 | * Answers whether there are any definitions in the tree. | |
| 555 | * | |
| 556 | * @return {@code true} when there are no definitions; {@code false} when | |
| 557 | * there's at least one definition. | |
| 558 | */ | |
| 559 | public boolean isEmpty() { | |
| 560 | return getTreeRoot().isEmpty(); | |
| 561 | } | |
| 562 | } | |
| 1 | 563 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents behaviours for reading and writing string definitions. This | |
| 32 | * class cannot have any direct hooks into the user interface, as it defines | |
| 33 | * entry points into the definition data model loaded into an object | |
| 34 | * hierarchy. That hierarchy is converted to a UI model using an adapter | |
| 35 | * pattern. | |
| 36 | */ | |
| 37 | public interface DefinitionSource { | |
| 38 | ||
| 39 | /** | |
| 40 | * Creates an object capable of producing view-based objects from this | |
| 41 | * definition source. | |
| 42 | * | |
| 43 | * @return A hierarchical tree suitable for displaying in the definition pane. | |
| 44 | */ | |
| 45 | TreeAdapter getTreeAdapter(); | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the error message, if any, that occurred while loading the | |
| 49 | * definition source. | |
| 50 | * | |
| 51 | * @return The empty string if no error occurred, otherwise the error message. | |
| 52 | */ | |
| 53 | default String getError() { | |
| 54 | return ""; | |
| 55 | } | |
| 56 | } | |
| 1 | 57 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | ||
| 32 | import java.util.Stack; | |
| 33 | import java.util.function.BiFunction; | |
| 34 | ||
| 35 | import static java.text.Normalizer.Form.NFD; | |
| 36 | import static java.text.Normalizer.normalize; | |
| 37 | ||
| 38 | /** | |
| 39 | * Provides behaviour afforded to definition keys and corresponding value. | |
| 40 | * | |
| 41 | * @param <T> The type of {@link TreeItem} (usually string). | |
| 42 | */ | |
| 43 | public class DefinitionTreeItem<T> extends TreeItem<T> { | |
| 44 | ||
| 45 | /** | |
| 46 | * Constructs a new item with a default value. | |
| 47 | * | |
| 48 | * @param value Passed up to superclass. | |
| 49 | */ | |
| 50 | public DefinitionTreeItem( final T value ) { | |
| 51 | super( value ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Finds a leaf starting at the current node with text that matches the given | |
| 56 | * value. Search is performed case-sensitively. | |
| 57 | * | |
| 58 | * @param text The text to match against each leaf in the tree. | |
| 59 | * @return The leaf that has a value exactly matching the given text. | |
| 60 | */ | |
| 61 | public DefinitionTreeItem<T> findLeafExact( final String text ) { | |
| 62 | return findLeaf( text, DefinitionTreeItem::valueEquals ); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Finds a leaf starting at the current node with text that matches the given | |
| 67 | * value. Search is performed case-sensitively. | |
| 68 | * | |
| 69 | * @param text The text to match against each leaf in the tree. | |
| 70 | * @return The leaf that has a value that contains the given text. | |
| 71 | */ | |
| 72 | public DefinitionTreeItem<T> findLeafContains( final String text ) { | |
| 73 | return findLeaf( text, DefinitionTreeItem::valueContains ); | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Finds a leaf starting at the current node with text that matches the given | |
| 78 | * value. Search is performed case-insensitively. | |
| 79 | * | |
| 80 | * @param text The text to match against each leaf in the tree. | |
| 81 | * @return The leaf that has a value that contains the given text. | |
| 82 | */ | |
| 83 | public DefinitionTreeItem<T> findLeafContainsNoCase( final String text ) { | |
| 84 | return findLeaf( text, DefinitionTreeItem::valueContainsNoCase ); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Finds a leaf starting at the current node with text that matches the given | |
| 89 | * value. Search is performed case-sensitively. | |
| 90 | * | |
| 91 | * @param text The text to match against each leaf in the tree. | |
| 92 | * @return The leaf that has a value that starts with the given text. | |
| 93 | */ | |
| 94 | public DefinitionTreeItem<T> findLeafStartsWith( final String text ) { | |
| 95 | return findLeaf( text, DefinitionTreeItem::valueStartsWith ); | |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * Finds a leaf starting at the current node with text that matches the given | |
| 100 | * value. | |
| 101 | * | |
| 102 | * @param text The text to match against each leaf in the tree. | |
| 103 | * @param findMode What algorithm is used to match the given text. | |
| 104 | * @return The leaf that has a value starting with the given text, or {@code | |
| 105 | * null} if there was no match found. | |
| 106 | */ | |
| 107 | public DefinitionTreeItem<T> findLeaf( | |
| 108 | final String text, | |
| 109 | final BiFunction<DefinitionTreeItem<T>, String, Boolean> findMode ) { | |
| 110 | final var stack = new Stack<DefinitionTreeItem<T>>(); | |
| 111 | stack.push( this ); | |
| 112 | ||
| 113 | // Don't hunt for blank (empty) keys. | |
| 114 | boolean found = text.isBlank(); | |
| 115 | ||
| 116 | while( !found && !stack.isEmpty() ) { | |
| 117 | final var node = stack.pop(); | |
| 118 | ||
| 119 | for( final var child : node.getChildren() ) { | |
| 120 | final var result = (DefinitionTreeItem<T>) child; | |
| 121 | ||
| 122 | if( result.isLeaf() ) { | |
| 123 | if( found = findMode.apply( result, text ) ) { | |
| 124 | return result; | |
| 125 | } | |
| 126 | } | |
| 127 | else { | |
| 128 | stack.push( result ); | |
| 129 | } | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 133 | return null; | |
| 134 | } | |
| 135 | ||
| 136 | /** | |
| 137 | * Returns the value of the string without diacritic marks. | |
| 138 | * | |
| 139 | * @return A non-null, possibly empty string. | |
| 140 | */ | |
| 141 | private String getDiacriticlessValue() { | |
| 142 | return normalize( getValue().toString(), NFD ) | |
| 143 | .replaceAll( "\\p{M}", "" ); | |
| 144 | } | |
| 145 | ||
| 146 | /** | |
| 147 | * Returns true if this node is a leaf and its value equals the given text. | |
| 148 | * | |
| 149 | * @param s The text to compare against the node value. | |
| 150 | * @return true Node is a leaf and its value equals the given value. | |
| 151 | */ | |
| 152 | private boolean valueEquals( final String s ) { | |
| 153 | return isLeaf() && getValue().equals( s ); | |
| 154 | } | |
| 155 | ||
| 156 | /** | |
| 157 | * Returns true if this node is a leaf and its value contains the given text. | |
| 158 | * | |
| 159 | * @param s The text to compare against the node value. | |
| 160 | * @return true Node is a leaf and its value contains the given value. | |
| 161 | */ | |
| 162 | private boolean valueContains( final String s ) { | |
| 163 | return isLeaf() && getDiacriticlessValue().contains( s ); | |
| 164 | } | |
| 165 | ||
| 166 | /** | |
| 167 | * Returns true if this node is a leaf and its value contains the given text. | |
| 168 | * | |
| 169 | * @param s The text to compare against the node value. | |
| 170 | * @return true Node is a leaf and its value contains the given value. | |
| 171 | */ | |
| 172 | private boolean valueContainsNoCase( final String s ) { | |
| 173 | return isLeaf() && getDiacriticlessValue() | |
| 174 | .toLowerCase().contains( s.toLowerCase() ); | |
| 175 | } | |
| 176 | ||
| 177 | /** | |
| 178 | * Returns true if this node is a leaf and its value starts with the given | |
| 179 | * text. | |
| 180 | * | |
| 181 | * @param s The text to compare against the node value. | |
| 182 | * @return true Node is a leaf and its value starts with the given value. | |
| 183 | */ | |
| 184 | private boolean valueStartsWith( final String s ) { | |
| 185 | return isLeaf() && getDiacriticlessValue().startsWith( s ); | |
| 186 | } | |
| 187 | ||
| 188 | /** | |
| 189 | * Returns the path for this node, with nodes made distinct using the | |
| 190 | * separator character. This uses two loops: one for pushing nodes onto a | |
| 191 | * stack and one for popping them off to create the path in desired order. | |
| 192 | * | |
| 193 | * @return A non-null string, possibly empty. | |
| 194 | */ | |
| 195 | public String toPath() { | |
| 196 | return TreeItemAdapter.toPath( getParent() ); | |
| 197 | } | |
| 198 | ||
| 199 | /** | |
| 200 | * Answers whether there are any definitions in this tree. | |
| 201 | * | |
| 202 | * @return {@code true} when there are no definitions in the tree; {@code | |
| 203 | * false} when there is at least one definition present. | |
| 204 | */ | |
| 205 | public boolean isEmpty() { | |
| 206 | return getChildren().isEmpty(); | |
| 207 | } | |
| 208 | } | |
| 1 | 209 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for parsing structured document formats. | |
| 32 | * | |
| 33 | * @param <T> The type of "node" for the document's object model. | |
| 34 | */ | |
| 35 | public interface DocumentParser<T> { | |
| 36 | ||
| 37 | /** | |
| 38 | * Parses a document into a nested object hierarchy. The object returned | |
| 39 | * from this call must be the root node in the document tree. | |
| 40 | * | |
| 41 | * @return The document's root node, which may be empty but never null. | |
| 42 | */ | |
| 43 | T getDocumentRoot(); | |
| 44 | } | |
| 1 | 45 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.Node; | |
| 31 | import javafx.scene.control.TextField; | |
| 32 | import javafx.scene.control.cell.TextFieldTreeCell; | |
| 33 | import javafx.util.StringConverter; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for fixing a focus lost bug in the JavaFX implementation. | |
| 37 | * See https://bugs.openjdk.java.net/browse/JDK-8089514 for details. | |
| 38 | * This implementation borrows from the official documentation on creating | |
| 39 | * tree views: https://docs.oracle.com/javafx/2/ui_controls/tree-view.htm | |
| 40 | */ | |
| 41 | public class FocusAwareTextFieldTreeCell extends TextFieldTreeCell<String> { | |
| 42 | private TextField mTextField; | |
| 43 | ||
| 44 | public FocusAwareTextFieldTreeCell( | |
| 45 | final StringConverter<String> converter ) { | |
| 46 | super( converter ); | |
| 47 | } | |
| 48 | ||
| 49 | @Override | |
| 50 | public void startEdit() { | |
| 51 | super.startEdit(); | |
| 52 | var textField = mTextField; | |
| 53 | ||
| 54 | if( textField == null ) { | |
| 55 | textField = createTextField(); | |
| 56 | } | |
| 57 | else { | |
| 58 | textField.setText( getItem() ); | |
| 59 | } | |
| 60 | ||
| 61 | setText( null ); | |
| 62 | setGraphic( textField ); | |
| 63 | textField.selectAll(); | |
| 64 | textField.requestFocus(); | |
| 65 | ||
| 66 | // When the focus is lost, commit the edit then close the input field. | |
| 67 | // This fixes the unexpected behaviour when user clicks away. | |
| 68 | textField.focusedProperty().addListener( ( l, o, n ) -> { | |
| 69 | if( !n ) { | |
| 70 | commitEdit( mTextField.getText() ); | |
| 71 | } | |
| 72 | } ); | |
| 73 | ||
| 74 | mTextField = textField; | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public void cancelEdit() { | |
| 79 | super.cancelEdit(); | |
| 80 | setText( getItem() ); | |
| 81 | setGraphic( getTreeItem().getGraphic() ); | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | public void updateItem( String item, boolean empty ) { | |
| 86 | super.updateItem( item, empty ); | |
| 87 | ||
| 88 | String text = null; | |
| 89 | Node graphic = null; | |
| 90 | ||
| 91 | if( !empty ) { | |
| 92 | if( isEditing() ) { | |
| 93 | final var textField = mTextField; | |
| 94 | ||
| 95 | if( textField != null ) { | |
| 96 | textField.setText( getString() ); | |
| 97 | } | |
| 98 | ||
| 99 | graphic = textField; | |
| 100 | } | |
| 101 | else { | |
| 102 | text = getString(); | |
| 103 | graphic = getTreeItem().getGraphic(); | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | setText( text ); | |
| 108 | setGraphic( graphic ); | |
| 109 | } | |
| 110 | ||
| 111 | private TextField createTextField() { | |
| 112 | final var textField = new TextField( getString() ); | |
| 113 | ||
| 114 | textField.setOnKeyReleased( t -> { | |
| 115 | switch( t.getCode() ) { | |
| 116 | case ENTER -> commitEdit( textField.getText() ); | |
| 117 | case ESCAPE -> cancelEdit(); | |
| 118 | } | |
| 119 | } ); | |
| 120 | ||
| 121 | return textField; | |
| 122 | } | |
| 123 | ||
| 124 | private String getString() { | |
| 125 | return getConverter().toString( getItem() ); | |
| 126 | } | |
| 127 | } | |
| 1 | 128 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import com.scrivenvar.sigils.YamlSigilOperator; | |
| 31 | ||
| 32 | import java.util.Map; | |
| 33 | import java.util.regex.Matcher; | |
| 34 | ||
| 35 | import static com.scrivenvar.sigils.YamlSigilOperator.REGEX_PATTERN; | |
| 36 | ||
| 37 | /** | |
| 38 | * Responsible for performing string interpolation on key/value pairs stored | |
| 39 | * in a map. The values in the map can use a delimited syntax to refer to | |
| 40 | * keys in the map. | |
| 41 | */ | |
| 42 | public class MapInterpolator { | |
| 43 | private static final int GROUP_DELIMITED = 1; | |
| 44 | ||
| 45 | /** | |
| 46 | * Empty. | |
| 47 | */ | |
| 48 | private MapInterpolator() { | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * Performs string interpolation on the values in the given map. This will | |
| 53 | * change any value in the map that contains a variable that matches | |
| 54 | * {@link YamlSigilOperator#REGEX_PATTERN}. | |
| 55 | * | |
| 56 | * @param map Contains values that represent references to keys. | |
| 57 | */ | |
| 58 | public static void interpolate( final Map<String, String> map ) { | |
| 59 | map.replaceAll( ( k, v ) -> resolve( map, v ) ); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Given a value with zero or more key references, this will resolve all | |
| 64 | * the values, recursively. If a key cannot be dereferenced, the value will | |
| 65 | * contain the key name. | |
| 66 | * | |
| 67 | * @param map Map to search for keys when resolving key references. | |
| 68 | * @param value Value containing zero or more key references | |
| 69 | * @return The given value with all embedded key references interpolated. | |
| 70 | */ | |
| 71 | private static String resolve( | |
| 72 | final Map<String, String> map, String value ) { | |
| 73 | final Matcher matcher = REGEX_PATTERN.matcher( value ); | |
| 74 | ||
| 75 | while( matcher.find() ) { | |
| 76 | final String keyName = matcher.group( GROUP_DELIMITED ); | |
| 77 | final String mapValue = map.get( keyName ); | |
| 78 | final String keyValue = mapValue == null | |
| 79 | ? keyName | |
| 80 | : resolve( map, mapValue ); | |
| 81 | ||
| 82 | value = value.replace( keyName, keyValue ); | |
| 83 | } | |
| 84 | ||
| 85 | return value; | |
| 86 | } | |
| 87 | } | |
| 1 | 88 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | import javafx.scene.control.TreeView; | |
| 32 | ||
| 33 | /** | |
| 34 | * Indicates that this is the top-most {@link TreeItem}. This class allows | |
| 35 | * the {@link TreeItemAdapter} to ignore the topmost definition. Such | |
| 36 | * contortions are necessary because {@link TreeView} requires a root item | |
| 37 | * that isn't part of the user's definition file. | |
| 38 | * <p> | |
| 39 | * Another approach would be to associate object pairs per {@link TreeItem}, | |
| 40 | * but that would be a waste of memory since the only "exception" case is | |
| 41 | * the root {@link TreeItem}. | |
| 42 | * </p> | |
| 43 | * | |
| 44 | * @param <T> The type of {@link TreeItem} to store in the {@link TreeView}. | |
| 45 | */ | |
| 46 | public class RootTreeItem<T> extends DefinitionTreeItem<T> { | |
| 47 | /** | |
| 48 | * Default constructor, calls the superclass, no other behaviour. | |
| 49 | * | |
| 50 | * @param value The {@link TreeItem} node name to construct the superclass. | |
| 51 | * @see TreeItemAdapter#toMap(TreeItem) for details on how this | |
| 52 | * class is used. | |
| 53 | */ | |
| 54 | public RootTreeItem( final T value ) { | |
| 55 | super( value ); | |
| 56 | } | |
| 57 | } | |
| 1 | 58 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import javafx.scene.control.TreeItem; | |
| 31 | ||
| 32 | import java.io.IOException; | |
| 33 | import java.nio.file.Path; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for converting an object hierarchy into a {@link TreeItem} | |
| 37 | * hierarchy. | |
| 38 | */ | |
| 39 | public interface TreeAdapter { | |
| 40 | /** | |
| 41 | * Adapts the document produced by the given parser into a {@link TreeItem} | |
| 42 | * object that can be presented to the user within a GUI. | |
| 43 | * | |
| 44 | * @param root The default root node name. | |
| 45 | * @return The parsed document in a {@link TreeItem} that can be displayed | |
| 46 | * in a panel. | |
| 47 | */ | |
| 48 | TreeItem<String> adapt( String root ); | |
| 49 | ||
| 50 | /** | |
| 51 | * Exports the given root node to the given path. | |
| 52 | * | |
| 53 | * @param root The root node to export. | |
| 54 | * @param path Where to persist the data. | |
| 55 | * @throws IOException Could not write the data to the given path. | |
| 56 | */ | |
| 57 | void export( TreeItem<String> root, Path path ) throws IOException; | |
| 58 | } | |
| 1 | 59 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.scrivenvar.sigils.YamlSigilOperator; | |
| 32 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 33 | import javafx.scene.control.TreeItem; | |
| 34 | import javafx.scene.control.TreeView; | |
| 35 | ||
| 36 | import java.util.HashMap; | |
| 37 | import java.util.Iterator; | |
| 38 | import java.util.Map; | |
| 39 | import java.util.Stack; | |
| 40 | ||
| 41 | import static com.scrivenvar.Constants.DEFAULT_MAP_SIZE; | |
| 42 | ||
| 43 | /** | |
| 44 | * Given a {@link TreeItem}, this will generate a flat map with all the | |
| 45 | * values in the tree recursively interpolated. The application integrates | |
| 46 | * definition files as follows: | |
| 47 | * <ol> | |
| 48 | * <li>Load YAML file into {@link JsonNode} hierarchy.</li> | |
| 49 | * <li>Convert JsonNode to a {@link TreeItem} hierarchy.</li> | |
| 50 | * <li>Interpolate {@link TreeItem} hierarchy as a flat map.</li> | |
| 51 | * <li>Substitute flat map variables into document as required.</li> | |
| 52 | * </ol> | |
| 53 | * | |
| 54 | * <p> | |
| 55 | * This class is responsible for producing the interpolated flat map. This | |
| 56 | * allows dynamic edits of the {@link TreeView} to be displayed in the | |
| 57 | * {@link HTMLPreviewPane} without having to reload the definition file. | |
| 58 | * Reloading the definition file would work, but has a number of drawbacks. | |
| 59 | * </p> | |
| 60 | */ | |
| 61 | public class TreeItemAdapter { | |
| 62 | /** | |
| 63 | * Separates YAML definition keys (e.g., the dots in {@code $root.node.var$}). | |
| 64 | */ | |
| 65 | public static final String SEPARATOR = "."; | |
| 66 | ||
| 67 | /** | |
| 68 | * Default buffer length for keys ({@link StringBuilder} has 16 character | |
| 69 | * buffer) that should be large enough for most keys to avoid reallocating | |
| 70 | * memory to increase the {@link StringBuilder}'s buffer. | |
| 71 | */ | |
| 72 | public static final int DEFAULT_KEY_LENGTH = 64; | |
| 73 | ||
| 74 | /** | |
| 75 | * In-order traversal of a {@link TreeItem} hierarchy, exposing each item | |
| 76 | * as a consecutive list. | |
| 77 | */ | |
| 78 | private static final class TreeIterator | |
| 79 | implements Iterator<TreeItem<String>> { | |
| 80 | private final Stack<TreeItem<String>> mStack = new Stack<>(); | |
| 81 | ||
| 82 | public TreeIterator( final TreeItem<String> root ) { | |
| 83 | if( root != null ) { | |
| 84 | mStack.push( root ); | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | @Override | |
| 89 | public boolean hasNext() { | |
| 90 | return !mStack.isEmpty(); | |
| 91 | } | |
| 92 | ||
| 93 | @Override | |
| 94 | public TreeItem<String> next() { | |
| 95 | final TreeItem<String> next = mStack.pop(); | |
| 96 | next.getChildren().forEach( mStack::push ); | |
| 97 | ||
| 98 | return next; | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | private TreeItemAdapter() { | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Iterate over a given root node (at any level of the tree) and process each | |
| 107 | * leaf node into a flat map. Values must be interpolated separately. | |
| 108 | */ | |
| 109 | public static Map<String, String> toMap( final TreeItem<String> root ) { | |
| 110 | final Map<String, String> map = new HashMap<>( DEFAULT_MAP_SIZE ); | |
| 111 | final TreeIterator iterator = new TreeIterator( root ); | |
| 112 | ||
| 113 | iterator.forEachRemaining( item -> { | |
| 114 | if( item.isLeaf() ) { | |
| 115 | map.put( toPath( item.getParent() ), item.getValue() ); | |
| 116 | } | |
| 117 | } ); | |
| 118 | ||
| 119 | return map; | |
| 120 | } | |
| 121 | ||
| 122 | ||
| 123 | /** | |
| 124 | * For a given node, this will ascend the tree to generate a key name | |
| 125 | * that is associated with the leaf node's value. | |
| 126 | * | |
| 127 | * @param node Ascendants represent the key to this node's value. | |
| 128 | * @param <T> Data type that the {@link TreeItem} contains. | |
| 129 | * @return The string representation of the node's unique key. | |
| 130 | */ | |
| 131 | public static <T> String toPath( TreeItem<T> node ) { | |
| 132 | assert node != null; | |
| 133 | ||
| 134 | final StringBuilder key = new StringBuilder( DEFAULT_KEY_LENGTH ); | |
| 135 | final Stack<TreeItem<T>> stack = new Stack<>(); | |
| 136 | ||
| 137 | while( node != null && !(node instanceof RootTreeItem) ) { | |
| 138 | stack.push( node ); | |
| 139 | node = node.getParent(); | |
| 140 | } | |
| 141 | ||
| 142 | // Gets set at end of first iteration (to avoid an if condition). | |
| 143 | String separator = ""; | |
| 144 | ||
| 145 | while( !stack.empty() ) { | |
| 146 | final T subkey = stack.pop().getValue(); | |
| 147 | key.append( separator ); | |
| 148 | key.append( subkey ); | |
| 149 | separator = SEPARATOR; | |
| 150 | } | |
| 151 | ||
| 152 | return YamlSigilOperator.entoken( key.toString() ); | |
| 153 | } | |
| 154 | } | |
| 1 | 155 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition.yaml; | |
| 29 | ||
| 30 | import com.scrivenvar.definition.DefinitionSource; | |
| 31 | import com.scrivenvar.definition.TreeAdapter; | |
| 32 | ||
| 33 | import java.nio.file.Path; | |
| 34 | ||
| 35 | /** | |
| 36 | * Represents a definition data source for YAML files. | |
| 37 | */ | |
| 38 | public class YamlDefinitionSource implements DefinitionSource { | |
| 39 | ||
| 40 | private final YamlTreeAdapter mYamlTreeAdapter; | |
| 41 | ||
| 42 | /** | |
| 43 | * Constructs a new YAML definition source, populated from the given file. | |
| 44 | * | |
| 45 | * @param path Path to the YAML definition file. | |
| 46 | */ | |
| 47 | public YamlDefinitionSource( final Path path ) { | |
| 48 | assert path != null; | |
| 49 | ||
| 50 | mYamlTreeAdapter = new YamlTreeAdapter( path ); | |
| 51 | } | |
| 52 | ||
| 53 | @Override | |
| 54 | public TreeAdapter getTreeAdapter() { | |
| 55 | return mYamlTreeAdapter; | |
| 56 | } | |
| 57 | ||
| 58 | @Override | |
| 59 | public String getError() { | |
| 60 | return getYamlParser().getError(); | |
| 61 | } | |
| 62 | ||
| 63 | private YamlParser getYamlParser() { | |
| 64 | return mYamlTreeAdapter.getYamlParser(); | |
| 65 | } | |
| 66 | } | |
| 1 | 67 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition.yaml; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 32 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | |
| 33 | import com.scrivenvar.Messages; | |
| 34 | import com.scrivenvar.definition.DocumentParser; | |
| 35 | ||
| 36 | import java.io.InputStream; | |
| 37 | import java.nio.file.Files; | |
| 38 | import java.nio.file.Path; | |
| 39 | ||
| 40 | import static com.scrivenvar.Constants.STATUS_BAR_OK; | |
| 41 | ||
| 42 | /** | |
| 43 | * Responsible for reading a YAML document into an object hierarchy. | |
| 44 | */ | |
| 45 | public class YamlParser implements DocumentParser<JsonNode> { | |
| 46 | ||
| 47 | /** | |
| 48 | * Error that occurred while parsing. | |
| 49 | */ | |
| 50 | private String mError; | |
| 51 | ||
| 52 | /** | |
| 53 | * Start of the Universe (the YAML document node that contains all others). | |
| 54 | */ | |
| 55 | private final JsonNode mDocumentRoot; | |
| 56 | ||
| 57 | /** | |
| 58 | * Creates a new YamlParser instance that attempts to parse the contents | |
| 59 | * of the YAML document given from a path. In the event that the file either | |
| 60 | * does not exist or is empty, a fake | |
| 61 | * | |
| 62 | * @param path Path to a file containing YAML data to parse. | |
| 63 | */ | |
| 64 | public YamlParser( final Path path ) { | |
| 65 | assert path != null; | |
| 66 | mDocumentRoot = parse( path ); | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Returns the parent node for the entire YAML document tree. | |
| 71 | * | |
| 72 | * @return The document root, never {@code null}. | |
| 73 | */ | |
| 74 | @Override | |
| 75 | public JsonNode getDocumentRoot() { | |
| 76 | return mDocumentRoot; | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Parses the given path containing YAML data into an object hierarchy. | |
| 81 | * | |
| 82 | * @param path {@link Path} to the YAML resource to parse. | |
| 83 | * @return The parsed contents, or an empty object hierarchy. | |
| 84 | */ | |
| 85 | private JsonNode parse( final Path path ) { | |
| 86 | try( final InputStream in = Files.newInputStream( path ) ) { | |
| 87 | setError( Messages.get( STATUS_BAR_OK ) ); | |
| 88 | ||
| 89 | return new ObjectMapper( new YAMLFactory() ).readTree( in ); | |
| 90 | } catch( final Exception e ) { | |
| 91 | setError( Messages.get( "yaml.error.open" ) ); | |
| 92 | ||
| 93 | // Ensure that a document root node exists by relying on the | |
| 94 | // default failure condition when processing. This is required | |
| 95 | // because the input stream could not be read. | |
| 96 | return new ObjectMapper().createObjectNode(); | |
| 97 | } | |
| 98 | } | |
| 99 | ||
| 100 | private void setError( final String error ) { | |
| 101 | mError = error; | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * Returns the last error message, if any, that occurred during parsing. | |
| 106 | * | |
| 107 | * @return The error message or the empty string if no error occurred. | |
| 108 | */ | |
| 109 | public String getError() { | |
| 110 | return mError; | |
| 111 | } | |
| 112 | } | |
| 1 | 113 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.definition.yaml; | |
| 29 | ||
| 30 | import com.fasterxml.jackson.databind.JsonNode; | |
| 31 | import com.fasterxml.jackson.databind.node.ObjectNode; | |
| 32 | import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; | |
| 33 | import com.scrivenvar.definition.RootTreeItem; | |
| 34 | import com.scrivenvar.definition.TreeAdapter; | |
| 35 | import com.scrivenvar.definition.DefinitionTreeItem; | |
| 36 | import javafx.scene.control.TreeItem; | |
| 37 | import javafx.scene.control.TreeView; | |
| 38 | ||
| 39 | import java.io.IOException; | |
| 40 | import java.nio.file.Path; | |
| 41 | import java.util.Map.Entry; | |
| 42 | ||
| 43 | /** | |
| 44 | * Transforms a JsonNode hierarchy into a tree that can be displayed in a user | |
| 45 | * interface and vice-versa. | |
| 46 | */ | |
| 47 | public class YamlTreeAdapter implements TreeAdapter { | |
| 48 | private final YamlParser mParser; | |
| 49 | ||
| 50 | /** | |
| 51 | * Constructs a new instance that will use the given path to read | |
| 52 | * the object hierarchy from a data source. | |
| 53 | * | |
| 54 | * @param path Path to YAML contents to parse. | |
| 55 | */ | |
| 56 | public YamlTreeAdapter( final Path path ) { | |
| 57 | mParser = new YamlParser( path ); | |
| 58 | } | |
| 59 | ||
| 60 | @Override | |
| 61 | public void export( final TreeItem<String> treeItem, final Path path ) | |
| 62 | throws IOException { | |
| 63 | final YAMLMapper mapper = new YAMLMapper(); | |
| 64 | final ObjectNode root = mapper.createObjectNode(); | |
| 65 | ||
| 66 | // Iterate over the root item's children. The root item is used by the | |
| 67 | // application to ensure definitions can always be added to a tree, as | |
| 68 | // such it is not meant to be exported, only its children. | |
| 69 | for( final TreeItem<String> child : treeItem.getChildren() ) { | |
| 70 | export( child, root ); | |
| 71 | } | |
| 72 | ||
| 73 | // Writes as UTF8 by default. | |
| 74 | mapper.writeValue( path.toFile(), root ); | |
| 75 | } | |
| 76 | ||
| 77 | /** | |
| 78 | * Recursive method to generate an object hierarchy that represents the | |
| 79 | * given {@link TreeItem} hierarchy. | |
| 80 | * | |
| 81 | * @param item The {@link TreeItem} to reproduce as an object hierarchy. | |
| 82 | * @param node The {@link ObjectNode} to update to reflect the | |
| 83 | * {@link TreeItem} hierarchy. | |
| 84 | */ | |
| 85 | private void export( final TreeItem<String> item, ObjectNode node ) { | |
| 86 | final var children = item.getChildren(); | |
| 87 | ||
| 88 | // If the current item has more than one non-leaf child, it's an | |
| 89 | // object node and must become a new nested object. | |
| 90 | if( !(children.size() == 1 && children.get( 0 ).isLeaf()) ) { | |
| 91 | node = node.putObject( item.getValue() ); | |
| 92 | } | |
| 93 | ||
| 94 | for( final TreeItem<String> child : children ) { | |
| 95 | if( child.isLeaf() ) { | |
| 96 | node.put( item.getValue(), child.getValue() ); | |
| 97 | } | |
| 98 | else { | |
| 99 | export( child, node ); | |
| 100 | } | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * Converts a YAML document to a {@link TreeItem} based on the document | |
| 106 | * keys. Only the first document in the stream is adapted. | |
| 107 | * | |
| 108 | * @param root Root {@link TreeItem} node name. | |
| 109 | * @return A {@link TreeItem} populated with all the keys in the YAML | |
| 110 | * document. | |
| 111 | */ | |
| 112 | public TreeItem<String> adapt( final String root ) { | |
| 113 | final JsonNode rootNode = getYamlParser().getDocumentRoot(); | |
| 114 | final TreeItem<String> rootItem = createRootTreeItem( root ); | |
| 115 | ||
| 116 | rootItem.setExpanded( true ); | |
| 117 | adapt( rootNode, rootItem ); | |
| 118 | return rootItem; | |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Iterate over a given root node (at any level of the tree) and adapt each | |
| 123 | * leaf node. | |
| 124 | * | |
| 125 | * @param rootNode A JSON node (YAML node) to adapt. | |
| 126 | * @param rootItem The tree item to use as the root when processing the node. | |
| 127 | */ | |
| 128 | private void adapt( | |
| 129 | final JsonNode rootNode, final TreeItem<String> rootItem ) { | |
| 130 | rootNode.fields().forEachRemaining( | |
| 131 | ( Entry<String, JsonNode> leaf ) -> adapt( leaf, rootItem ) | |
| 132 | ); | |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * Recursively adapt each rootNode to a corresponding rootItem. | |
| 137 | * | |
| 138 | * @param rootNode The node to adapt. | |
| 139 | * @param rootItem The item to adapt using the node's key. | |
| 140 | */ | |
| 141 | private void adapt( | |
| 142 | final Entry<String, JsonNode> rootNode, | |
| 143 | final TreeItem<String> rootItem ) { | |
| 144 | final JsonNode leafNode = rootNode.getValue(); | |
| 145 | final String key = rootNode.getKey(); | |
| 146 | final TreeItem<String> leaf = createTreeItem( key ); | |
| 147 | ||
| 148 | if( leafNode.isValueNode() ) { | |
| 149 | leaf.getChildren().add( createTreeItem( rootNode.getValue().asText() ) ); | |
| 150 | } | |
| 151 | ||
| 152 | rootItem.getChildren().add( leaf ); | |
| 153 | ||
| 154 | if( leafNode.isObject() ) { | |
| 155 | adapt( leafNode, leaf ); | |
| 156 | } | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Creates a new {@link TreeItem} that can be added to the {@link TreeView}. | |
| 161 | * | |
| 162 | * @param value The node's value. | |
| 163 | * @return A new {@link TreeItem}, never {@code null}. | |
| 164 | */ | |
| 165 | private TreeItem<String> createTreeItem( final String value ) { | |
| 166 | return new DefinitionTreeItem<>( value ); | |
| 167 | } | |
| 168 | ||
| 169 | /** | |
| 170 | * Creates a new {@link TreeItem} that is intended to be the root-level item | |
| 171 | * added to the {@link TreeView}. This allows the root item to be | |
| 172 | * distinguished from the other items so that reference keys do not include | |
| 173 | * "Definition" as part of their name. | |
| 174 | * | |
| 175 | * @param value The node's value. | |
| 176 | * @return A new {@link TreeItem}, never {@code null}. | |
| 177 | */ | |
| 178 | private TreeItem<String> createRootTreeItem( final String value ) { | |
| 179 | return new RootTreeItem<>( value ); | |
| 180 | } | |
| 181 | ||
| 182 | public YamlParser getYamlParser() { | |
| 183 | return mParser; | |
| 184 | } | |
| 185 | } | |
| 1 | 186 |
| 1 | /* | |
| 2 | * Copyright 2017 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.dialogs; | |
| 29 | ||
| 30 | import static com.scrivenvar.Messages.get; | |
| 31 | import com.scrivenvar.service.events.impl.ButtonOrderPane; | |
| 32 | import static javafx.scene.control.ButtonType.CANCEL; | |
| 33 | import static javafx.scene.control.ButtonType.OK; | |
| 34 | import javafx.scene.control.Dialog; | |
| 35 | import javafx.stage.Window; | |
| 36 | ||
| 37 | /** | |
| 38 | * Superclass that abstracts common behaviours for all dialogs. | |
| 39 | * | |
| 40 | * @param <T> The type of dialog to create (usually String). | |
| 41 | */ | |
| 42 | public abstract class AbstractDialog<T> extends Dialog<T> { | |
| 43 | ||
| 44 | /** | |
| 45 | * Ensures that all dialogs can be closed. | |
| 46 | * | |
| 47 | * @param owner The parent window of this dialog. | |
| 48 | * @param title The messages title to display in the title bar. | |
| 49 | */ | |
| 50 | @SuppressWarnings( "OverridableMethodCallInConstructor" ) | |
| 51 | public AbstractDialog( final Window owner, final String title ) { | |
| 52 | setTitle( get( title ) ); | |
| 53 | setResizable( true ); | |
| 54 | ||
| 55 | initOwner( owner ); | |
| 56 | initCloseAction(); | |
| 57 | initDialogPane(); | |
| 58 | initDialogButtons(); | |
| 59 | initComponents(); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Initialize the component layout. | |
| 64 | */ | |
| 65 | protected abstract void initComponents(); | |
| 66 | ||
| 67 | /** | |
| 68 | * Set the dialog to use a button order pane with an OK and a CANCEL button. | |
| 69 | */ | |
| 70 | protected void initDialogPane() { | |
| 71 | setDialogPane( new ButtonOrderPane() ); | |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Set an OK and CANCEL button on the dialog. | |
| 76 | */ | |
| 77 | protected void initDialogButtons() { | |
| 78 | getDialogPane().getButtonTypes().addAll( OK, CANCEL ); | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Attaches a setOnCloseRequest to the dialog's [X] button so that the user | |
| 83 | * can always close the window, even if there's an error. | |
| 84 | */ | |
| 85 | protected final void initCloseAction() { | |
| 86 | final Window window = getDialogPane().getScene().getWindow(); | |
| 87 | window.setOnCloseRequest( event -> window.hide() ); | |
| 88 | } | |
| 89 | } | |
| 1 | 90 |
| 1 | /* | |
| 2 | * Copyright 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.dialogs; | |
| 28 | ||
| 29 | import static com.scrivenvar.Messages.get; | |
| 30 | import com.scrivenvar.controls.BrowseFileButton; | |
| 31 | import com.scrivenvar.controls.EscapeTextField; | |
| 32 | import java.nio.file.Path; | |
| 33 | import javafx.application.Platform; | |
| 34 | import javafx.beans.binding.Bindings; | |
| 35 | import javafx.beans.property.SimpleStringProperty; | |
| 36 | import javafx.beans.property.StringProperty; | |
| 37 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 38 | import static javafx.scene.control.ButtonType.OK; | |
| 39 | import javafx.scene.control.DialogPane; | |
| 40 | import javafx.scene.control.Label; | |
| 41 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 42 | import javafx.stage.Window; | |
| 43 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 44 | ||
| 45 | /** | |
| 46 | * Dialog to enter a markdown image. | |
| 47 | */ | |
| 48 | public class ImageDialog extends AbstractDialog<String> { | |
| 49 | ||
| 50 | private final StringProperty image = new SimpleStringProperty(); | |
| 51 | ||
| 52 | public ImageDialog( final Window owner, final Path basePath ) { | |
| 53 | super(owner, "Dialog.image.title" ); | |
| 54 | ||
| 55 | final DialogPane dialogPane = getDialogPane(); | |
| 56 | dialogPane.setContent( pane ); | |
| 57 | ||
| 58 | linkBrowseFileButton.setBasePath( basePath ); | |
| 59 | linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( get( "Dialog.image.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) ); | |
| 60 | linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() ); | |
| 61 | ||
| 62 | dialogPane.lookupButton( OK ).disableProperty().bind( | |
| 63 | urlField.escapedTextProperty().isEmpty() | |
| 64 | .or( textField.escapedTextProperty().isEmpty() ) ); | |
| 65 | ||
| 66 | image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 67 | .then( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 68 | .otherwise( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) ); | |
| 69 | previewField.textProperty().bind( image ); | |
| 70 | ||
| 71 | setResultConverter( dialogButton -> { | |
| 72 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 73 | return (data == ButtonData.OK_DONE) ? image.get() : null; | |
| 74 | } ); | |
| 75 | ||
| 76 | Platform.runLater( () -> { | |
| 77 | urlField.requestFocus(); | |
| 78 | ||
| 79 | if( urlField.getText().startsWith( "http://" ) ) { | |
| 80 | urlField.selectRange( "http://".length(), urlField.getLength() ); | |
| 81 | } | |
| 82 | } ); | |
| 83 | } | |
| 84 | ||
| 85 | @Override | |
| 86 | protected void initComponents() { | |
| 87 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 88 | pane = new MigPane(); | |
| 89 | Label urlLabel = new Label(); | |
| 90 | urlField = new EscapeTextField(); | |
| 91 | linkBrowseFileButton = new BrowseFileButton(); | |
| 92 | Label textLabel = new Label(); | |
| 93 | textField = new EscapeTextField(); | |
| 94 | Label titleLabel = new Label(); | |
| 95 | titleField = new EscapeTextField(); | |
| 96 | Label previewLabel = new Label(); | |
| 97 | previewField = new Label(); | |
| 98 | ||
| 99 | //======== pane ======== | |
| 100 | { | |
| 101 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" ); | |
| 102 | pane.setRows( "[][][][]" ); | |
| 103 | ||
| 104 | //---- urlLabel ---- | |
| 105 | urlLabel.setText( get( "Dialog.image.urlLabel.text" ) ); | |
| 106 | pane.add( urlLabel, "cell 0 0" ); | |
| 107 | ||
| 108 | //---- urlField ---- | |
| 109 | urlField.setEscapeCharacters( "()" ); | |
| 110 | urlField.setText( "http://yourlink.com" ); | |
| 111 | urlField.setPromptText( "http://yourlink.com" ); | |
| 112 | pane.add( urlField, "cell 1 0" ); | |
| 113 | pane.add( linkBrowseFileButton, "cell 2 0" ); | |
| 114 | ||
| 115 | //---- textLabel ---- | |
| 116 | textLabel.setText( get( "Dialog.image.textLabel.text" ) ); | |
| 117 | pane.add( textLabel, "cell 0 1" ); | |
| 118 | ||
| 119 | //---- textField ---- | |
| 120 | textField.setEscapeCharacters( "[]" ); | |
| 121 | pane.add( textField, "cell 1 1 2 1" ); | |
| 122 | ||
| 123 | //---- titleLabel ---- | |
| 124 | titleLabel.setText( get( "Dialog.image.titleLabel.text" ) ); | |
| 125 | pane.add( titleLabel, "cell 0 2" ); | |
| 126 | pane.add( titleField, "cell 1 2 2 1" ); | |
| 127 | ||
| 128 | //---- previewLabel ---- | |
| 129 | previewLabel.setText( get( "Dialog.image.previewLabel.text" ) ); | |
| 130 | pane.add( previewLabel, "cell 0 3" ); | |
| 131 | pane.add( previewField, "cell 1 3 2 1" ); | |
| 132 | } | |
| 133 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 134 | } | |
| 135 | ||
| 136 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 137 | private MigPane pane; | |
| 138 | private EscapeTextField urlField; | |
| 139 | private BrowseFileButton linkBrowseFileButton; | |
| 140 | private EscapeTextField textField; | |
| 141 | private EscapeTextField titleField; | |
| 142 | private Label previewField; | |
| 143 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 144 | } | |
| 1 | 145 |
| 1 | JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8" | |
| 2 | ||
| 3 | new FormModel { | |
| 4 | "i18n.bundlePackage": "com.scrivendor" | |
| 5 | "i18n.bundleName": "messages" | |
| 6 | "i18n.autoExternalize": true | |
| 7 | "i18n.keyPrefix": "ImageDialog" | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) { | |
| 11 | "$layoutConstraints": "" | |
| 12 | "$columnConstraints": "[shrink 0,fill][300,grow,fill][fill]" | |
| 13 | "$rowConstraints": "[][][][]" | |
| 14 | } ) { | |
| 15 | name: "pane" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "urlLabel" | |
| 18 | "text": new FormMessage( null, "ImageDialog.urlLabel.text" ) | |
| 19 | auxiliary() { | |
| 20 | "JavaCodeGenerator.variableLocal": true | |
| 21 | } | |
| 22 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 23 | "value": "cell 0 0" | |
| 24 | } ) | |
| 25 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 26 | name: "urlField" | |
| 27 | "escapeCharacters": "()" | |
| 28 | "text": "http://yourlink.com" | |
| 29 | "promptText": "http://yourlink.com" | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 1 0" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) { | |
| 34 | name: "linkBrowseFileButton" | |
| 35 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 36 | "value": "cell 2 0" | |
| 37 | } ) | |
| 38 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 39 | name: "textLabel" | |
| 40 | "text": new FormMessage( null, "ImageDialog.textLabel.text" ) | |
| 41 | auxiliary() { | |
| 42 | "JavaCodeGenerator.variableLocal": true | |
| 43 | } | |
| 44 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 45 | "value": "cell 0 1" | |
| 46 | } ) | |
| 47 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 48 | name: "textField" | |
| 49 | "escapeCharacters": "[]" | |
| 50 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 51 | "value": "cell 1 1 2 1" | |
| 52 | } ) | |
| 53 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 54 | name: "titleLabel" | |
| 55 | "text": new FormMessage( null, "ImageDialog.titleLabel.text" ) | |
| 56 | auxiliary() { | |
| 57 | "JavaCodeGenerator.variableLocal": true | |
| 58 | } | |
| 59 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 60 | "value": "cell 0 2" | |
| 61 | } ) | |
| 62 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 63 | name: "titleField" | |
| 64 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 65 | "value": "cell 1 2 2 1" | |
| 66 | } ) | |
| 67 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 68 | name: "previewLabel" | |
| 69 | "text": new FormMessage( null, "ImageDialog.previewLabel.text" ) | |
| 70 | auxiliary() { | |
| 71 | "JavaCodeGenerator.variableLocal": true | |
| 72 | } | |
| 73 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 74 | "value": "cell 0 3" | |
| 75 | } ) | |
| 76 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 77 | name: "previewField" | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 1 3 2 1" | |
| 80 | } ) | |
| 81 | }, new FormLayoutConstraints( null ) { | |
| 82 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 83 | "size": new javafx.geometry.Dimension2D( 500.0, 300.0 ) | |
| 84 | } ) | |
| 85 | } | |
| 86 | } | |
| 1 | 87 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.dialogs; | |
| 29 | ||
| 30 | import com.scrivenvar.controls.EscapeTextField; | |
| 31 | import com.scrivenvar.editors.markdown.HyperlinkModel; | |
| 32 | import javafx.application.Platform; | |
| 33 | import javafx.beans.binding.Bindings; | |
| 34 | import javafx.beans.property.SimpleStringProperty; | |
| 35 | import javafx.beans.property.StringProperty; | |
| 36 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 37 | import javafx.scene.control.DialogPane; | |
| 38 | import javafx.scene.control.Label; | |
| 39 | import javafx.stage.Window; | |
| 40 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 41 | ||
| 42 | import static com.scrivenvar.Messages.get; | |
| 43 | import static javafx.scene.control.ButtonType.OK; | |
| 44 | ||
| 45 | /** | |
| 46 | * Dialog to enter a markdown link. | |
| 47 | */ | |
| 48 | public class LinkDialog extends AbstractDialog<String> { | |
| 49 | ||
| 50 | private final StringProperty link = new SimpleStringProperty(); | |
| 51 | ||
| 52 | public LinkDialog( | |
| 53 | final Window owner, final HyperlinkModel hyperlink ) { | |
| 54 | super( owner, "Dialog.link.title" ); | |
| 55 | ||
| 56 | final DialogPane dialogPane = getDialogPane(); | |
| 57 | dialogPane.setContent( pane ); | |
| 58 | ||
| 59 | dialogPane.lookupButton( OK ).disableProperty().bind( | |
| 60 | urlField.escapedTextProperty().isEmpty() ); | |
| 61 | ||
| 62 | textField.setText( hyperlink.getText() ); | |
| 63 | urlField.setText( hyperlink.getUrl() ); | |
| 64 | titleField.setText( hyperlink.getTitle() ); | |
| 65 | ||
| 66 | link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 67 | .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 68 | .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() ) | |
| 69 | .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) | |
| 70 | .otherwise( urlField.escapedTextProperty() ) ) ); | |
| 71 | ||
| 72 | setResultConverter( dialogButton -> { | |
| 73 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 74 | return (data == ButtonData.OK_DONE) ? link.get() : null; | |
| 75 | } ); | |
| 76 | ||
| 77 | Platform.runLater( () -> { | |
| 78 | urlField.requestFocus(); | |
| 79 | urlField.selectRange( 0, urlField.getLength() ); | |
| 80 | } ); | |
| 81 | } | |
| 82 | ||
| 83 | @Override | |
| 84 | protected void initComponents() { | |
| 85 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 86 | pane = new MigPane(); | |
| 87 | Label urlLabel = new Label(); | |
| 88 | urlField = new EscapeTextField(); | |
| 89 | Label textLabel = new Label(); | |
| 90 | textField = new EscapeTextField(); | |
| 91 | Label titleLabel = new Label(); | |
| 92 | titleField = new EscapeTextField(); | |
| 93 | ||
| 94 | //======== pane ======== | |
| 95 | { | |
| 96 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" ); | |
| 97 | pane.setRows( "[][][][]" ); | |
| 98 | ||
| 99 | //---- urlLabel ---- | |
| 100 | urlLabel.setText( get( "Dialog.link.urlLabel.text" ) ); | |
| 101 | pane.add( urlLabel, "cell 0 0" ); | |
| 102 | ||
| 103 | //---- urlField ---- | |
| 104 | urlField.setEscapeCharacters( "()" ); | |
| 105 | pane.add( urlField, "cell 1 0" ); | |
| 106 | ||
| 107 | //---- textLabel ---- | |
| 108 | textLabel.setText( get( "Dialog.link.textLabel.text" ) ); | |
| 109 | pane.add( textLabel, "cell 0 1" ); | |
| 110 | ||
| 111 | //---- textField ---- | |
| 112 | textField.setEscapeCharacters( "[]" ); | |
| 113 | pane.add( textField, "cell 1 1 3 1" ); | |
| 114 | ||
| 115 | //---- titleLabel ---- | |
| 116 | titleLabel.setText( get( "Dialog.link.titleLabel.text" ) ); | |
| 117 | pane.add( titleLabel, "cell 0 2" ); | |
| 118 | pane.add( titleField, "cell 1 2 3 1" ); | |
| 119 | } | |
| 120 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 121 | } | |
| 122 | ||
| 123 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 124 | private MigPane pane; | |
| 125 | private EscapeTextField urlField; | |
| 126 | private EscapeTextField textField; | |
| 127 | private EscapeTextField titleField; | |
| 128 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 129 | } | |
| 1 | 130 |
| 1 | JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8" | |
| 2 | ||
| 3 | new FormModel { | |
| 4 | "i18n.bundlePackage": "com.scrivendor" | |
| 5 | "i18n.bundleName": "messages" | |
| 6 | "i18n.autoExternalize": true | |
| 7 | "i18n.keyPrefix": "LinkDialog" | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) { | |
| 11 | "$layoutConstraints": "" | |
| 12 | "$columnConstraints": "[shrink 0,fill][300,grow,fill][fill][fill]" | |
| 13 | "$rowConstraints": "[][][][]" | |
| 14 | } ) { | |
| 15 | name: "pane" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "urlLabel" | |
| 18 | "text": new FormMessage( null, "LinkDialog.urlLabel.text" ) | |
| 19 | auxiliary() { | |
| 20 | "JavaCodeGenerator.variableLocal": true | |
| 21 | } | |
| 22 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 23 | "value": "cell 0 0" | |
| 24 | } ) | |
| 25 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 26 | name: "urlField" | |
| 27 | "escapeCharacters": "()" | |
| 28 | "text": "http://yourlink.com" | |
| 29 | "promptText": "http://yourlink.com" | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 1 0" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.BrowseDirectoryButton" ) { | |
| 34 | name: "linkBrowseDirectoyButton" | |
| 35 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 36 | "value": "cell 2 0" | |
| 37 | } ) | |
| 38 | add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) { | |
| 39 | name: "linkBrowseFileButton" | |
| 40 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 41 | "value": "cell 3 0" | |
| 42 | } ) | |
| 43 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 44 | name: "textLabel" | |
| 45 | "text": new FormMessage( null, "LinkDialog.textLabel.text" ) | |
| 46 | auxiliary() { | |
| 47 | "JavaCodeGenerator.variableLocal": true | |
| 48 | } | |
| 49 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 50 | "value": "cell 0 1" | |
| 51 | } ) | |
| 52 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 53 | name: "textField" | |
| 54 | "escapeCharacters": "[]" | |
| 55 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 56 | "value": "cell 1 1 3 1" | |
| 57 | } ) | |
| 58 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 59 | name: "titleLabel" | |
| 60 | "text": new FormMessage( null, "LinkDialog.titleLabel.text" ) | |
| 61 | auxiliary() { | |
| 62 | "JavaCodeGenerator.variableLocal": true | |
| 63 | } | |
| 64 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 65 | "value": "cell 0 2" | |
| 66 | } ) | |
| 67 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 68 | name: "titleField" | |
| 69 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 70 | "value": "cell 1 2 3 1" | |
| 71 | } ) | |
| 72 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 73 | name: "previewLabel" | |
| 74 | "text": new FormMessage( null, "LinkDialog.previewLabel.text" ) | |
| 75 | auxiliary() { | |
| 76 | "JavaCodeGenerator.variableLocal": true | |
| 77 | } | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 0 3" | |
| 80 | } ) | |
| 81 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 82 | name: "previewField" | |
| 83 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 84 | "value": "cell 1 3 3 1" | |
| 85 | } ) | |
| 86 | }, new FormLayoutConstraints( null ) { | |
| 87 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 88 | "size": new javafx.geometry.Dimension2D( 500.0, 300.0 ) | |
| 89 | } ) | |
| 90 | } | |
| 91 | } | |
| 1 | 92 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractFileFactory; | |
| 31 | import com.scrivenvar.sigils.RSigilOperator; | |
| 32 | import com.scrivenvar.sigils.SigilOperator; | |
| 33 | import com.scrivenvar.sigils.YamlSigilOperator; | |
| 34 | ||
| 35 | import java.nio.file.Path; | |
| 36 | ||
| 37 | /** | |
| 38 | * Responsible for creating a definition name decorator suited to a particular | |
| 39 | * file type. | |
| 40 | */ | |
| 41 | public class DefinitionDecoratorFactory extends AbstractFileFactory { | |
| 42 | ||
| 43 | private DefinitionDecoratorFactory() { | |
| 44 | } | |
| 45 | ||
| 46 | public static SigilOperator newInstance( final Path path ) { | |
| 47 | final var factory = new DefinitionDecoratorFactory(); | |
| 48 | ||
| 49 | return switch( factory.lookup( path ) ) { | |
| 50 | case RMARKDOWN, RXML -> new RSigilOperator(); | |
| 51 | default -> new YamlSigilOperator(); | |
| 52 | }; | |
| 53 | } | |
| 54 | } | |
| 1 | 55 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors; | |
| 29 | ||
| 30 | import com.scrivenvar.FileEditorTab; | |
| 31 | import com.scrivenvar.definition.DefinitionPane; | |
| 32 | import com.scrivenvar.definition.DefinitionTreeItem; | |
| 33 | import com.scrivenvar.sigils.SigilOperator; | |
| 34 | import javafx.scene.control.TreeItem; | |
| 35 | import javafx.scene.input.KeyEvent; | |
| 36 | import org.fxmisc.richtext.StyledTextArea; | |
| 37 | ||
| 38 | import java.nio.file.Path; | |
| 39 | import java.text.BreakIterator; | |
| 40 | ||
| 41 | import static com.scrivenvar.Constants.*; | |
| 42 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 43 | import static java.lang.Character.isWhitespace; | |
| 44 | import static javafx.scene.input.KeyCode.SPACE; | |
| 45 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; | |
| 46 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 47 | ||
| 48 | /** | |
| 49 | * Provides the logic for injecting variable names within the editor. | |
| 50 | */ | |
| 51 | public final class DefinitionNameInjector { | |
| 52 | ||
| 53 | /** | |
| 54 | * Recipient of name injections. | |
| 55 | */ | |
| 56 | private FileEditorTab mTab; | |
| 57 | ||
| 58 | /** | |
| 59 | * Initiates double-click events. | |
| 60 | */ | |
| 61 | private final DefinitionPane mDefinitionPane; | |
| 62 | ||
| 63 | /** | |
| 64 | * Initializes the variable name injector against the given pane. | |
| 65 | * | |
| 66 | * @param pane The definition panel to listen to for double-click events. | |
| 67 | */ | |
| 68 | public DefinitionNameInjector( final DefinitionPane pane ) { | |
| 69 | mDefinitionPane = pane; | |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * Trap Control+Space. | |
| 74 | * | |
| 75 | * @param tab Editor where variable names get injected. | |
| 76 | */ | |
| 77 | public void addListener( final FileEditorTab tab ) { | |
| 78 | assert tab != null; | |
| 79 | mTab = tab; | |
| 80 | ||
| 81 | tab.getEditorPane().addKeyboardListener( | |
| 82 | keyPressed( SPACE, CONTROL_DOWN ), | |
| 83 | this::autoinsert | |
| 84 | ); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Inserts the currently selected variable from the {@link DefinitionPane}. | |
| 89 | */ | |
| 90 | public void injectSelectedItem() { | |
| 91 | final var pane = getDefinitionPane(); | |
| 92 | final TreeItem<String> item = pane.getSelectedItem(); | |
| 93 | ||
| 94 | if( item.isLeaf() ) { | |
| 95 | final var leaf = pane.findLeafExact( item.getValue() ); | |
| 96 | final var editor = getEditor(); | |
| 97 | ||
| 98 | editor.insertText( editor.getCaretPosition(), decorate( leaf ) ); | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Pressing Control+SPACE will find a node that matches the current word and | |
| 104 | * substitute the definition reference. | |
| 105 | */ | |
| 106 | private void autoinsert() { | |
| 107 | final String paragraph = getCaretParagraph(); | |
| 108 | final int[] bounds = getWordBoundariesAtCaret(); | |
| 109 | ||
| 110 | try { | |
| 111 | if( isEmptyDefinitionPane() ) { | |
| 112 | alert( STATUS_DEFINITION_EMPTY ); | |
| 113 | } | |
| 114 | else { | |
| 115 | final String word = paragraph.substring( bounds[ 0 ], bounds[ 1 ] ); | |
| 116 | ||
| 117 | if( word.isBlank() ) { | |
| 118 | alert( STATUS_DEFINITION_BLANK ); | |
| 119 | } | |
| 120 | else { | |
| 121 | final var leaf = findLeaf( word ); | |
| 122 | ||
| 123 | if( leaf == null ) { | |
| 124 | alert( STATUS_DEFINITION_MISSING, word ); | |
| 125 | } | |
| 126 | else { | |
| 127 | replaceText( bounds[ 0 ], bounds[ 1 ], decorate( leaf ) ); | |
| 128 | expand( leaf ); | |
| 129 | } | |
| 130 | } | |
| 131 | } | |
| 132 | } catch( final Exception ignored ) { | |
| 133 | alert( STATUS_DEFINITION_BLANK ); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Pressing Control+SPACE will find a node that matches the current word and | |
| 139 | * substitute the definition reference. | |
| 140 | * | |
| 141 | * @param e Ignored -- it can only be Control+SPACE. | |
| 142 | */ | |
| 143 | private void autoinsert( final KeyEvent e ) { | |
| 144 | autoinsert(); | |
| 145 | } | |
| 146 | ||
| 147 | /** | |
| 148 | * Finds the start and end indexes for the word in the current paragraph | |
| 149 | * where the caret is located. There are a few different scenarios, where | |
| 150 | * the caret can be at: the start, end, or middle of a word; also, the | |
| 151 | * caret can be at the end or beginning of a punctuated word. | |
| 152 | */ | |
| 153 | private int[] getWordBoundariesAtCaret() { | |
| 154 | final var paragraph = getCaretParagraph(); | |
| 155 | final var length = paragraph.length(); | |
| 156 | int offset = getCurrentCaretColumn(); | |
| 157 | ||
| 158 | int began = offset; | |
| 159 | int ended = offset; | |
| 160 | ||
| 161 | while( began > 0 && !isWhitespace( paragraph.charAt( began - 1 ) ) ) { | |
| 162 | began--; | |
| 163 | } | |
| 164 | ||
| 165 | while( ended < length && !isWhitespace( paragraph.charAt( ended ) ) ) { | |
| 166 | ended++; | |
| 167 | } | |
| 168 | ||
| 169 | final var iterator = BreakIterator.getWordInstance(); | |
| 170 | iterator.setText( paragraph ); | |
| 171 | ||
| 172 | while( began < length && iterator.isBoundary( began + 1 ) ) { | |
| 173 | began++; | |
| 174 | } | |
| 175 | ||
| 176 | while( ended > 0 && iterator.isBoundary( ended - 1 ) ) { | |
| 177 | ended--; | |
| 178 | } | |
| 179 | ||
| 180 | return new int[]{began, ended}; | |
| 181 | } | |
| 182 | ||
| 183 | /** | |
| 184 | * Decorates a {@link TreeItem} using the syntax specific to the type of | |
| 185 | * document being edited. | |
| 186 | * | |
| 187 | * @param leaf The path to the leaf (the definition key) to be decorated. | |
| 188 | */ | |
| 189 | private String decorate( final DefinitionTreeItem<String> leaf ) { | |
| 190 | return decorate( leaf.toPath() ); | |
| 191 | } | |
| 192 | ||
| 193 | /** | |
| 194 | * Decorates a variable using the syntax specific to the type of document | |
| 195 | * being edited. | |
| 196 | * | |
| 197 | * @param variable The variable to decorate in dot-notation without any | |
| 198 | * start or end sigils present. | |
| 199 | */ | |
| 200 | private String decorate( final String variable ) { | |
| 201 | return getVariableDecorator().apply( variable ); | |
| 202 | } | |
| 203 | ||
| 204 | /** | |
| 205 | * Updates the text at the given position within the current paragraph. | |
| 206 | * | |
| 207 | * @param posBegan The starting index in the paragraph text to replace. | |
| 208 | * @param posEnded The ending index in the paragraph text to replace. | |
| 209 | * @param text Overwrite the paragraph substring with this text. | |
| 210 | */ | |
| 211 | private void replaceText( | |
| 212 | final int posBegan, final int posEnded, final String text ) { | |
| 213 | final int p = getCurrentParagraph(); | |
| 214 | ||
| 215 | getEditor().replaceText( p, posBegan, p, posEnded, text ); | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * Returns the caret's current paragraph position. | |
| 220 | * | |
| 221 | * @return A number greater than or equal to 0. | |
| 222 | */ | |
| 223 | private int getCurrentParagraph() { | |
| 224 | return getEditor().getCurrentParagraph(); | |
| 225 | } | |
| 226 | ||
| 227 | /** | |
| 228 | * Returns the text for the paragraph that contains the caret. | |
| 229 | * | |
| 230 | * @return A non-null string, possibly empty. | |
| 231 | */ | |
| 232 | private String getCaretParagraph() { | |
| 233 | return getEditor().getText( getCurrentParagraph() ); | |
| 234 | } | |
| 235 | ||
| 236 | /** | |
| 237 | * Returns the caret position within the current paragraph. | |
| 238 | * | |
| 239 | * @return A value from 0 to the length of the current paragraph. | |
| 240 | */ | |
| 241 | private int getCurrentCaretColumn() { | |
| 242 | return getEditor().getCaretColumn(); | |
| 243 | } | |
| 244 | ||
| 245 | /** | |
| 246 | * Looks for the given word, matching first by exact, next by a starts-with | |
| 247 | * condition with diacritics replaced, then by containment. | |
| 248 | * | |
| 249 | * @param word The word to match by: exact, at the beginning, or containment. | |
| 250 | * @return The matching {@link DefinitionTreeItem} for the given word, or | |
| 251 | * {@code null} if none found. | |
| 252 | */ | |
| 253 | @SuppressWarnings("ConstantConditions") | |
| 254 | private DefinitionTreeItem<String> findLeaf( final String word ) { | |
| 255 | assert word != null; | |
| 256 | ||
| 257 | final var pane = getDefinitionPane(); | |
| 258 | DefinitionTreeItem<String> leaf = null; | |
| 259 | ||
| 260 | leaf = leaf == null ? pane.findLeafExact( word ) : leaf; | |
| 261 | leaf = leaf == null ? pane.findLeafStartsWith( word ) : leaf; | |
| 262 | leaf = leaf == null ? pane.findLeafContains( word ) : leaf; | |
| 263 | leaf = leaf == null ? pane.findLeafContainsNoCase( word ) : leaf; | |
| 264 | ||
| 265 | return leaf; | |
| 266 | } | |
| 267 | ||
| 268 | /** | |
| 269 | * Answers whether there are any definitions in the tree. | |
| 270 | * | |
| 271 | * @return {@code true} when there are no definitions; {@code false} when | |
| 272 | * there's at least one definition. | |
| 273 | */ | |
| 274 | private boolean isEmptyDefinitionPane() { | |
| 275 | return getDefinitionPane().isEmpty(); | |
| 276 | } | |
| 277 | ||
| 278 | /** | |
| 279 | * Collapses the tree then expands and selects the given node. | |
| 280 | * | |
| 281 | * @param node The node to expand. | |
| 282 | */ | |
| 283 | private void expand( final TreeItem<String> node ) { | |
| 284 | final DefinitionPane pane = getDefinitionPane(); | |
| 285 | pane.collapse(); | |
| 286 | pane.expand( node ); | |
| 287 | pane.select( node ); | |
| 288 | } | |
| 289 | ||
| 290 | /** | |
| 291 | * @return A variable decorator that corresponds to the given file type. | |
| 292 | */ | |
| 293 | private SigilOperator getVariableDecorator() { | |
| 294 | return DefinitionDecoratorFactory.newInstance( getFilename() ); | |
| 295 | } | |
| 296 | ||
| 297 | private Path getFilename() { | |
| 298 | return getFileEditorTab().getPath(); | |
| 299 | } | |
| 300 | ||
| 301 | private EditorPane getEditorPane() { | |
| 302 | return getFileEditorTab().getEditorPane(); | |
| 303 | } | |
| 304 | ||
| 305 | private StyledTextArea<?, ?> getEditor() { | |
| 306 | return getEditorPane().getEditor(); | |
| 307 | } | |
| 308 | ||
| 309 | public FileEditorTab getFileEditorTab() { | |
| 310 | return mTab; | |
| 311 | } | |
| 312 | ||
| 313 | private DefinitionPane getDefinitionPane() { | |
| 314 | return mDefinitionPane; | |
| 315 | } | |
| 316 | } | |
| 1 | 317 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.Options; | |
| 32 | import javafx.beans.property.IntegerProperty; | |
| 33 | import javafx.beans.property.ObjectProperty; | |
| 34 | import javafx.beans.property.SimpleObjectProperty; | |
| 35 | import javafx.beans.value.ChangeListener; | |
| 36 | import javafx.event.Event; | |
| 37 | import javafx.scene.control.ScrollPane; | |
| 38 | import javafx.scene.layout.Pane; | |
| 39 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 40 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 41 | import org.fxmisc.undo.UndoManager; | |
| 42 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 43 | import org.fxmisc.wellbehaved.event.Nodes; | |
| 44 | ||
| 45 | import java.nio.file.Path; | |
| 46 | import java.util.function.Consumer; | |
| 47 | ||
| 48 | import static com.scrivenvar.StatusBarNotifier.clearAlert; | |
| 49 | import static java.lang.String.format; | |
| 50 | import static javafx.application.Platform.runLater; | |
| 51 | import static org.fxmisc.wellbehaved.event.InputMap.consume; | |
| 52 | ||
| 53 | /** | |
| 54 | * Represents common editing features for various types of text editors. | |
| 55 | */ | |
| 56 | public class EditorPane extends Pane { | |
| 57 | ||
| 58 | private static final Options sOptions = Services.load( Options.class ); | |
| 59 | ||
| 60 | /** | |
| 61 | * Used when changing the text area font size. | |
| 62 | */ | |
| 63 | private static final String FMT_CSS_FONT_SIZE = "-fx-font-size: %dpt;"; | |
| 64 | ||
| 65 | private final StyleClassedTextArea mEditor = | |
| 66 | new StyleClassedTextArea( false ); | |
| 67 | private final VirtualizedScrollPane<StyleClassedTextArea> mScrollPane = | |
| 68 | new VirtualizedScrollPane<>( mEditor ); | |
| 69 | private final ObjectProperty<Path> mPath = new SimpleObjectProperty<>(); | |
| 70 | ||
| 71 | public EditorPane() { | |
| 72 | getScrollPane().setVbarPolicy( ScrollPane.ScrollBarPolicy.ALWAYS ); | |
| 73 | fontsSizeProperty().addListener( | |
| 74 | ( l, o, n ) -> setFontSize( n.intValue() ) | |
| 75 | ); | |
| 76 | ||
| 77 | // Clear out any previous alerts after the user has typed. If the problem | |
| 78 | // persists, re-rendering the document will re-raise the error. If there | |
| 79 | // was no previous error, clearing the alert is essentially a no-op. | |
| 80 | mEditor.textProperty().addListener( | |
| 81 | ( l, o, n ) -> clearAlert() | |
| 82 | ); | |
| 83 | } | |
| 84 | ||
| 85 | @Override | |
| 86 | public void requestFocus() { | |
| 87 | requestFocus( 3 ); | |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * There's a race-condition between displaying the {@link EditorPane} | |
| 92 | * and giving the {@link #mEditor} focus. Try to focus up to {@code max} | |
| 93 | * times before giving up. | |
| 94 | * | |
| 95 | * @param max The number of attempts to try to request focus. | |
| 96 | */ | |
| 97 | private void requestFocus( final int max ) { | |
| 98 | if( max > 0 ) { | |
| 99 | runLater( | |
| 100 | () -> { | |
| 101 | final var editor = getEditor(); | |
| 102 | ||
| 103 | if( !editor.isFocused() ) { | |
| 104 | editor.requestFocus(); | |
| 105 | requestFocus( max - 1 ); | |
| 106 | } | |
| 107 | } | |
| 108 | ); | |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | public void undo() { | |
| 113 | getUndoManager().undo(); | |
| 114 | } | |
| 115 | ||
| 116 | public void redo() { | |
| 117 | getUndoManager().redo(); | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Cuts the actively selected text; if no text is selected, this will cut | |
| 122 | * the entire paragraph. | |
| 123 | */ | |
| 124 | public void cut() { | |
| 125 | final var editor = getEditor(); | |
| 126 | final var selected = editor.getSelectedText(); | |
| 127 | ||
| 128 | if( selected == null || selected.isEmpty() ) { | |
| 129 | editor.selectParagraph(); | |
| 130 | } | |
| 131 | ||
| 132 | editor.cut(); | |
| 133 | } | |
| 134 | ||
| 135 | public void copy() { | |
| 136 | getEditor().copy(); | |
| 137 | } | |
| 138 | ||
| 139 | public void paste() { | |
| 140 | getEditor().paste(); | |
| 141 | } | |
| 142 | ||
| 143 | public void selectAll() { | |
| 144 | getEditor().selectAll(); | |
| 145 | } | |
| 146 | ||
| 147 | public UndoManager<?> getUndoManager() { | |
| 148 | return getEditor().getUndoManager(); | |
| 149 | } | |
| 150 | ||
| 151 | public String getText() { | |
| 152 | return getEditor().getText(); | |
| 153 | } | |
| 154 | ||
| 155 | public void setText( final String text ) { | |
| 156 | final var editor = getEditor(); | |
| 157 | editor.deselect(); | |
| 158 | editor.replaceText( text ); | |
| 159 | getUndoManager().mark(); | |
| 160 | } | |
| 161 | ||
| 162 | /** | |
| 163 | * Call to hook into changes to the text area. | |
| 164 | * | |
| 165 | * @param listener Receives editor text change events. | |
| 166 | */ | |
| 167 | public void addTextChangeListener( | |
| 168 | final ChangeListener<? super String> listener ) { | |
| 169 | getEditor().textProperty().addListener( listener ); | |
| 170 | } | |
| 171 | ||
| 172 | /** | |
| 173 | * Notifies observers when the caret changes paragraph. | |
| 174 | * | |
| 175 | * @param listener Receives change event. | |
| 176 | */ | |
| 177 | public void addCaretParagraphListener( | |
| 178 | final ChangeListener<? super Integer> listener ) { | |
| 179 | getEditor().currentParagraphProperty().addListener( listener ); | |
| 180 | } | |
| 181 | ||
| 182 | /** | |
| 183 | * Notifies observers when the caret changes position. | |
| 184 | * | |
| 185 | * @param listener Receives change event. | |
| 186 | */ | |
| 187 | public void addCaretPositionListener( | |
| 188 | final ChangeListener<? super Integer> listener ) { | |
| 189 | getEditor().caretPositionProperty().addListener( listener ); | |
| 190 | } | |
| 191 | ||
| 192 | /** | |
| 193 | * This method adds listeners to editor events. | |
| 194 | * | |
| 195 | * @param <T> The event type. | |
| 196 | * @param <U> The consumer type for the given event type. | |
| 197 | * @param event The event of interest. | |
| 198 | * @param consumer The method to call when the event happens. | |
| 199 | */ | |
| 200 | public <T extends Event, U extends T> void addKeyboardListener( | |
| 201 | final EventPattern<? super T, ? extends U> event, | |
| 202 | final Consumer<? super U> consumer ) { | |
| 203 | Nodes.addInputMap( getEditor(), consume( event, consumer ) ); | |
| 204 | } | |
| 205 | ||
| 206 | /** | |
| 207 | * Repositions the cursor and scroll bar to the top of the file. | |
| 208 | */ | |
| 209 | public void scrollToTop() { | |
| 210 | getEditor().moveTo( 0 ); | |
| 211 | getScrollPane().scrollYToPixel( 0 ); | |
| 212 | } | |
| 213 | ||
| 214 | public StyleClassedTextArea getEditor() { | |
| 215 | return mEditor; | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * Returns the scroll pane that contains the text area. | |
| 220 | * | |
| 221 | * @return The scroll pane that contains the content to edit. | |
| 222 | */ | |
| 223 | public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() { | |
| 224 | return mScrollPane; | |
| 225 | } | |
| 226 | ||
| 227 | public Path getPath() { | |
| 228 | return mPath.get(); | |
| 229 | } | |
| 230 | ||
| 231 | public void setPath( final Path path ) { | |
| 232 | mPath.set( path ); | |
| 233 | } | |
| 234 | ||
| 235 | /** | |
| 236 | * Sets the font size in points. | |
| 237 | * | |
| 238 | * @param size The new font size to use for the text editor. | |
| 239 | */ | |
| 240 | private void setFontSize( final int size ) { | |
| 241 | mEditor.setStyle( format( FMT_CSS_FONT_SIZE, size ) ); | |
| 242 | } | |
| 243 | ||
| 244 | /** | |
| 245 | * Returns the text editor font size property for handling font size change | |
| 246 | * events. | |
| 247 | */ | |
| 248 | private IntegerProperty fontsSizeProperty() { | |
| 249 | return sOptions.getUserPreferences().fontsSizeEditorProperty(); | |
| 250 | } | |
| 251 | } | |
| 1 | 252 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors.markdown; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | ||
| 32 | /** | |
| 33 | * Represents the model for a hyperlink: text, url, and title. | |
| 34 | */ | |
| 35 | public class HyperlinkModel { | |
| 36 | ||
| 37 | private String text; | |
| 38 | private String url; | |
| 39 | private String title; | |
| 40 | ||
| 41 | /** | |
| 42 | * Constructs a new hyperlink model in Markdown format by default with no | |
| 43 | * title (i.e., tooltip). | |
| 44 | * | |
| 45 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 46 | * @param url The destination URL (e.g., when clicked). | |
| 47 | */ | |
| 48 | public HyperlinkModel( final String text, final String url ) { | |
| 49 | this( text, url, null ); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Constructs a new hyperlink model for the given AST link. | |
| 54 | * | |
| 55 | * @param link A markdown link. | |
| 56 | */ | |
| 57 | public HyperlinkModel( final Link link ) { | |
| 58 | this( | |
| 59 | link.getText().toString(), | |
| 60 | link.getUrl().toString(), | |
| 61 | link.getTitle().toString() | |
| 62 | ); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Constructs a new hyperlink model in Markdown format by default. | |
| 67 | * | |
| 68 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 69 | * @param url The destination URL (e.g., when clicked). | |
| 70 | * @param title The hyperlink title (e.g., shown as a tooltip). | |
| 71 | */ | |
| 72 | public HyperlinkModel( final String text, final String url, | |
| 73 | final String title ) { | |
| 74 | setText( text ); | |
| 75 | setUrl( url ); | |
| 76 | setTitle( title ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Returns the string in Markdown format by default. | |
| 81 | * | |
| 82 | * @return A markdown version of the hyperlink. | |
| 83 | */ | |
| 84 | @Override | |
| 85 | public String toString() { | |
| 86 | String format = "%s%s%s"; | |
| 87 | ||
| 88 | if( hasText() ) { | |
| 89 | format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)"); | |
| 90 | } | |
| 91 | ||
| 92 | // Becomes ""+URL+"" if no text is set. | |
| 93 | // Becomes [TITLE]+(URL)+"" if no title is set. | |
| 94 | // Becomes [TITLE]+(URL+ \"TITLE\") if title is set. | |
| 95 | return String.format( format, getText(), getUrl(), getTitle() ); | |
| 96 | } | |
| 97 | ||
| 98 | public final void setText( final String text ) { | |
| 99 | this.text = nullSafe( text ); | |
| 100 | } | |
| 101 | ||
| 102 | public final void setUrl( final String url ) { | |
| 103 | this.url = nullSafe( url ); | |
| 104 | } | |
| 105 | ||
| 106 | public final void setTitle( final String title ) { | |
| 107 | this.title = nullSafe( title ); | |
| 108 | } | |
| 109 | ||
| 110 | /** | |
| 111 | * Answers whether text has been set for the hyperlink. | |
| 112 | * | |
| 113 | * @return true This is a text link. | |
| 114 | */ | |
| 115 | public boolean hasText() { | |
| 116 | return !getText().isEmpty(); | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * Answers whether a title (tooltip) has been set for the hyperlink. | |
| 121 | * | |
| 122 | * @return true There is a title. | |
| 123 | */ | |
| 124 | public boolean hasTitle() { | |
| 125 | return !getTitle().isEmpty(); | |
| 126 | } | |
| 127 | ||
| 128 | public String getText() { | |
| 129 | return this.text; | |
| 130 | } | |
| 131 | ||
| 132 | public String getUrl() { | |
| 133 | return this.url; | |
| 134 | } | |
| 135 | ||
| 136 | public String getTitle() { | |
| 137 | return this.title; | |
| 138 | } | |
| 139 | ||
| 140 | private String nullSafe( final String s ) { | |
| 141 | return s == null ? "" : s; | |
| 142 | } | |
| 143 | } | |
| 1 | 144 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors.markdown; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | import com.vladsch.flexmark.util.ast.Node; | |
| 32 | import com.vladsch.flexmark.util.ast.NodeVisitor; | |
| 33 | import com.vladsch.flexmark.util.ast.VisitHandler; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for extracting a hyperlink from the document so that the user | |
| 37 | * can edit the link within a dialog. | |
| 38 | */ | |
| 39 | public class LinkVisitor { | |
| 40 | ||
| 41 | private NodeVisitor visitor; | |
| 42 | private Link link; | |
| 43 | private final int offset; | |
| 44 | ||
| 45 | /** | |
| 46 | * Creates a hyperlink given an offset into a paragraph and the markdown AST | |
| 47 | * link node. | |
| 48 | * | |
| 49 | * @param index Index into the paragraph that indicates the hyperlink to | |
| 50 | * change. | |
| 51 | */ | |
| 52 | public LinkVisitor( final int index ) { | |
| 53 | this.offset = index; | |
| 54 | } | |
| 55 | ||
| 56 | public Link process( final Node root ) { | |
| 57 | getVisitor().visit( root ); | |
| 58 | return getLink(); | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * @param link Not null. | |
| 63 | */ | |
| 64 | private void visit( final Link link ) { | |
| 65 | final int began = link.getStartOffset(); | |
| 66 | final int ended = link.getEndOffset(); | |
| 67 | final int index = getOffset(); | |
| 68 | ||
| 69 | if( index >= began && index <= ended ) { | |
| 70 | setLink( link ); | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | private synchronized NodeVisitor getVisitor() { | |
| 75 | if( this.visitor == null ) { | |
| 76 | this.visitor = createVisitor(); | |
| 77 | } | |
| 78 | ||
| 79 | return this.visitor; | |
| 80 | } | |
| 81 | ||
| 82 | protected NodeVisitor createVisitor() { | |
| 83 | return new NodeVisitor( | |
| 84 | new VisitHandler<>( Link.class, LinkVisitor.this::visit ) ); | |
| 85 | } | |
| 86 | ||
| 87 | private Link getLink() { | |
| 88 | return this.link; | |
| 89 | } | |
| 90 | ||
| 91 | private void setLink( final Link link ) { | |
| 92 | this.link = link; | |
| 93 | } | |
| 94 | ||
| 95 | public int getOffset() { | |
| 96 | return this.offset; | |
| 97 | } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.editors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.dialogs.ImageDialog; | |
| 31 | import com.scrivenvar.dialogs.LinkDialog; | |
| 32 | import com.scrivenvar.editors.EditorPane; | |
| 33 | import com.scrivenvar.processors.markdown.BlockExtension; | |
| 34 | import com.scrivenvar.processors.markdown.MarkdownProcessor; | |
| 35 | import com.vladsch.flexmark.ast.Link; | |
| 36 | import com.vladsch.flexmark.html.renderer.AttributablePart; | |
| 37 | import com.vladsch.flexmark.util.ast.Node; | |
| 38 | import com.vladsch.flexmark.util.html.MutableAttributes; | |
| 39 | import javafx.scene.control.Dialog; | |
| 40 | import javafx.scene.control.IndexRange; | |
| 41 | import javafx.scene.input.KeyCode; | |
| 42 | import javafx.scene.input.KeyEvent; | |
| 43 | import javafx.stage.Window; | |
| 44 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 45 | ||
| 46 | import java.nio.file.Path; | |
| 47 | import java.util.ArrayList; | |
| 48 | import java.util.List; | |
| 49 | import java.util.regex.Matcher; | |
| 50 | import java.util.regex.Pattern; | |
| 51 | ||
| 52 | import static com.scrivenvar.Constants.STYLESHEET_MARKDOWN; | |
| 53 | import static com.scrivenvar.util.Utils.ltrim; | |
| 54 | import static com.scrivenvar.util.Utils.rtrim; | |
| 55 | import static javafx.scene.input.KeyCode.ENTER; | |
| 56 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; | |
| 57 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 58 | ||
| 59 | /** | |
| 60 | * Provides the ability to edit a text document. | |
| 61 | */ | |
| 62 | public class MarkdownEditorPane extends EditorPane { | |
| 63 | private static final Pattern PATTERN_AUTO_INDENT = Pattern.compile( | |
| 64 | "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" ); | |
| 65 | ||
| 66 | /** | |
| 67 | * Any of these followed by a space and a letter produce a line | |
| 68 | * by themselves. The ">" need not be followed by a space. | |
| 69 | */ | |
| 70 | private static final Pattern PATTERN_NEW_LINE = Pattern.compile( | |
| 71 | "^>|(((#+)|([*+\\-])|([1-9]\\.))\\s+).+" ); | |
| 72 | ||
| 73 | public MarkdownEditorPane() { | |
| 74 | initEditor(); | |
| 75 | } | |
| 76 | ||
| 77 | private void initEditor() { | |
| 78 | final StyleClassedTextArea textArea = getEditor(); | |
| 79 | ||
| 80 | textArea.setWrapText( true ); | |
| 81 | textArea.getStyleClass().add( "markdown-editor" ); | |
| 82 | textArea.getStylesheets().add( STYLESHEET_MARKDOWN ); | |
| 83 | ||
| 84 | addKeyboardListener( keyPressed( ENTER ), this::enterPressed ); | |
| 85 | addKeyboardListener( keyPressed( KeyCode.X, CONTROL_DOWN ), this::cut ); | |
| 86 | } | |
| 87 | ||
| 88 | public void insertLink() { | |
| 89 | insertObject( createLinkDialog() ); | |
| 90 | } | |
| 91 | ||
| 92 | public void insertImage() { | |
| 93 | insertObject( createImageDialog() ); | |
| 94 | } | |
| 95 | ||
| 96 | /** | |
| 97 | * Returns the editor's paragraph number that will be close to its HTML | |
| 98 | * paragraph ID. Ultimately this solution is flawed because there isn't | |
| 99 | * a straightforward correlation between the document being edited and | |
| 100 | * what is rendered. XML documents transformed through stylesheets have | |
| 101 | * no readily determined correlation. Images, tables, and other | |
| 102 | * objects affect the relative location of the current paragraph being | |
| 103 | * edited with respect to the preview pane. | |
| 104 | * <p> | |
| 105 | * See | |
| 106 | * {@link BlockExtension.IdAttributeProvider#setAttributes(Node, AttributablePart, MutableAttributes)}} | |
| 107 | * for details. | |
| 108 | * </p> | |
| 109 | * <p> | |
| 110 | * Injecting a token into the document, as per a previous version of the | |
| 111 | * application, can instruct the preview pane where to shift the viewport. | |
| 112 | * </p> | |
| 113 | * | |
| 114 | * @param paraIndex The paragraph index from the editor pane to scroll to | |
| 115 | * in the preview pane, which will be approximated if an | |
| 116 | * equivalent cannot be found. | |
| 117 | * @return A unique identifier that correlates to an equivalent paragraph | |
| 118 | * number once the Markdown is rendered into HTML. | |
| 119 | */ | |
| 120 | public int approximateParagraphId( final int paraIndex ) { | |
| 121 | final StyleClassedTextArea editor = getEditor(); | |
| 122 | final List<String> lines = new ArrayList<>( 4096 ); | |
| 123 | ||
| 124 | int i = 0; | |
| 125 | String prevText = ""; | |
| 126 | boolean withinFencedBlock = false; | |
| 127 | boolean withinCodeBlock = false; | |
| 128 | ||
| 129 | for( final var p : editor.getParagraphs() ) { | |
| 130 | if( i > paraIndex ) { | |
| 131 | break; | |
| 132 | } | |
| 133 | ||
| 134 | final String text = p.getText().replace( '>', ' ' ); | |
| 135 | if( text.startsWith( "```" ) ) { | |
| 136 | if( withinFencedBlock = !withinFencedBlock ) { | |
| 137 | lines.add( text ); | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | if( !withinFencedBlock ) { | |
| 142 | final boolean foundCodeBlock = text.startsWith( " " ); | |
| 143 | ||
| 144 | if( foundCodeBlock && !withinCodeBlock ) { | |
| 145 | lines.add( text ); | |
| 146 | withinCodeBlock = true; | |
| 147 | } | |
| 148 | else if( !foundCodeBlock ) { | |
| 149 | withinCodeBlock = false; | |
| 150 | } | |
| 151 | } | |
| 152 | ||
| 153 | if( !withinFencedBlock && !withinCodeBlock && | |
| 154 | ((!text.isBlank() && prevText.isBlank()) || | |
| 155 | PATTERN_NEW_LINE.matcher( text ).matches()) ) { | |
| 156 | lines.add( text ); | |
| 157 | } | |
| 158 | ||
| 159 | prevText = text; | |
| 160 | i++; | |
| 161 | } | |
| 162 | ||
| 163 | // Scrolling index is 1-based. | |
| 164 | return Math.max( lines.size() - 1, 0 ); | |
| 165 | } | |
| 166 | ||
| 167 | /** | |
| 168 | * Gets the index of the paragraph where the caret is positioned. | |
| 169 | * | |
| 170 | * @return The paragraph number for the caret. | |
| 171 | */ | |
| 172 | public int getCurrentParagraphIndex() { | |
| 173 | return getEditor().getCurrentParagraph(); | |
| 174 | } | |
| 175 | ||
| 176 | /** | |
| 177 | * @param leading Characters to insert at the beginning of the current | |
| 178 | * selection (or paragraph). | |
| 179 | * @param trailing Characters to insert at the end of the current selection | |
| 180 | * (or paragraph). | |
| 181 | */ | |
| 182 | public void surroundSelection( final String leading, final String trailing ) { | |
| 183 | surroundSelection( leading, trailing, null ); | |
| 184 | } | |
| 185 | ||
| 186 | /** | |
| 187 | * @param leading Characters to insert at the beginning of the current | |
| 188 | * selection (or paragraph). | |
| 189 | * @param trailing Characters to insert at the end of the current selection | |
| 190 | * (or paragraph). | |
| 191 | * @param hint Instructional text inserted within the leading and | |
| 192 | * trailing characters, provided no text is selected. | |
| 193 | */ | |
| 194 | public void surroundSelection( | |
| 195 | String leading, String trailing, final String hint ) { | |
| 196 | final StyleClassedTextArea textArea = getEditor(); | |
| 197 | ||
| 198 | // Note: not using textArea.insertText() to insert leading and trailing | |
| 199 | // because this would add two changes to undo history | |
| 200 | final IndexRange selection = textArea.getSelection(); | |
| 201 | int start = selection.getStart(); | |
| 202 | int end = selection.getEnd(); | |
| 203 | ||
| 204 | final String selectedText = textArea.getSelectedText(); | |
| 205 | ||
| 206 | String trimmedText = selectedText.trim(); | |
| 207 | if( trimmedText.length() < selectedText.length() ) { | |
| 208 | start += selectedText.indexOf( trimmedText ); | |
| 209 | end = start + trimmedText.length(); | |
| 210 | } | |
| 211 | ||
| 212 | // remove leading whitespaces from leading text if selection starts at zero | |
| 213 | if( start == 0 ) { | |
| 214 | leading = ltrim( leading ); | |
| 215 | } | |
| 216 | ||
| 217 | // remove trailing whitespaces from trailing text if selection ends at | |
| 218 | // text end | |
| 219 | if( end == textArea.getLength() ) { | |
| 220 | trailing = rtrim( trailing ); | |
| 221 | } | |
| 222 | ||
| 223 | // remove leading line separators from leading text | |
| 224 | // if there are line separators before the selected text | |
| 225 | if( leading.startsWith( "\n" ) ) { | |
| 226 | for( int i = start - 1; i >= 0 && leading.startsWith( "\n" ); i-- ) { | |
| 227 | if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) { | |
| 228 | break; | |
| 229 | } | |
| 230 | ||
| 231 | leading = leading.substring( 1 ); | |
| 232 | } | |
| 233 | } | |
| 234 | ||
| 235 | // remove trailing line separators from trailing or leading text | |
| 236 | // if there are line separators after the selected text | |
| 237 | final boolean trailingIsEmpty = trailing.isEmpty(); | |
| 238 | String str = trailingIsEmpty ? leading : trailing; | |
| 239 | ||
| 240 | if( str.endsWith( "\n" ) ) { | |
| 241 | final int length = textArea.getLength(); | |
| 242 | ||
| 243 | for( int i = end; i < length && str.endsWith( "\n" ); i++ ) { | |
| 244 | if( !"\n".equals( textArea.getText( i, i + 1 ) ) ) { | |
| 245 | break; | |
| 246 | } | |
| 247 | ||
| 248 | str = str.substring( 0, str.length() - 1 ); | |
| 249 | } | |
| 250 | ||
| 251 | if( trailingIsEmpty ) { | |
| 252 | leading = str; | |
| 253 | } | |
| 254 | else { | |
| 255 | trailing = str; | |
| 256 | } | |
| 257 | } | |
| 258 | ||
| 259 | int selStart = start + leading.length(); | |
| 260 | int selEnd = end + leading.length(); | |
| 261 | ||
| 262 | // insert hint text if selection is empty | |
| 263 | if( hint != null && trimmedText.isEmpty() ) { | |
| 264 | trimmedText = hint; | |
| 265 | selEnd = selStart + hint.length(); | |
| 266 | } | |
| 267 | ||
| 268 | // prevent undo merging with previous text entered by user | |
| 269 | getUndoManager().preventMerge(); | |
| 270 | ||
| 271 | // replace text and update selection | |
| 272 | textArea.replaceText( start, end, leading + trimmedText + trailing ); | |
| 273 | textArea.selectRange( selStart, selEnd ); | |
| 274 | } | |
| 275 | ||
| 276 | private void enterPressed( final KeyEvent e ) { | |
| 277 | final StyleClassedTextArea textArea = getEditor(); | |
| 278 | final String currentLine = | |
| 279 | textArea.getText( textArea.getCurrentParagraph() ); | |
| 280 | final Matcher matcher = PATTERN_AUTO_INDENT.matcher( currentLine ); | |
| 281 | ||
| 282 | String newText = "\n"; | |
| 283 | ||
| 284 | if( matcher.matches() ) { | |
| 285 | if( !matcher.group( 2 ).isEmpty() ) { | |
| 286 | // indent new line with same whitespace characters and list markers | |
| 287 | // as current line | |
| 288 | newText = newText.concat( matcher.group( 1 ) ); | |
| 289 | } | |
| 290 | else { | |
| 291 | // current line contains only whitespace characters and list markers | |
| 292 | // --> empty current line | |
| 293 | final int caretPosition = textArea.getCaretPosition(); | |
| 294 | textArea.selectRange( caretPosition - currentLine.length(), | |
| 295 | caretPosition ); | |
| 296 | } | |
| 297 | } | |
| 298 | ||
| 299 | textArea.replaceSelection( newText ); | |
| 300 | ||
| 301 | // Ensure that the window scrolls when Enter is pressed at the bottom of | |
| 302 | // the pane. | |
| 303 | textArea.requestFollowCaret(); | |
| 304 | } | |
| 305 | ||
| 306 | private void cut( final KeyEvent event ) { | |
| 307 | super.cut(); | |
| 308 | } | |
| 309 | ||
| 310 | /** | |
| 311 | * Returns one of: selected text, word under cursor, or parsed hyperlink from | |
| 312 | * the markdown AST. | |
| 313 | * | |
| 314 | * @return An instance containing the link URL and display text. | |
| 315 | */ | |
| 316 | private HyperlinkModel getHyperlink() { | |
| 317 | final StyleClassedTextArea textArea = getEditor(); | |
| 318 | final String selectedText = textArea.getSelectedText(); | |
| 319 | ||
| 320 | // Get the current paragraph, convert to Markdown nodes. | |
| 321 | final MarkdownProcessor mp = new MarkdownProcessor( null ); | |
| 322 | final int p = textArea.getCurrentParagraph(); | |
| 323 | final String paragraph = textArea.getText( p ); | |
| 324 | final Node node = mp.toNode( paragraph ); | |
| 325 | final LinkVisitor visitor = new LinkVisitor( textArea.getCaretColumn() ); | |
| 326 | final Link link = visitor.process( node ); | |
| 327 | ||
| 328 | if( link != null ) { | |
| 329 | textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() ); | |
| 330 | } | |
| 331 | ||
| 332 | return createHyperlinkModel( | |
| 333 | link, selectedText, "https://localhost" | |
| 334 | ); | |
| 335 | } | |
| 336 | ||
| 337 | @SuppressWarnings("SameParameterValue") | |
| 338 | private HyperlinkModel createHyperlinkModel( | |
| 339 | final Link link, final String selection, final String url ) { | |
| 340 | ||
| 341 | return link == null | |
| 342 | ? new HyperlinkModel( selection, url ) | |
| 343 | : new HyperlinkModel( link ); | |
| 344 | } | |
| 345 | ||
| 346 | private Path getParentPath() { | |
| 347 | final Path path = getPath(); | |
| 348 | return (path != null) ? path.getParent() : null; | |
| 349 | } | |
| 350 | ||
| 351 | private Dialog<String> createLinkDialog() { | |
| 352 | return new LinkDialog( getWindow(), getHyperlink() ); | |
| 353 | } | |
| 354 | ||
| 355 | private Dialog<String> createImageDialog() { | |
| 356 | return new ImageDialog( getWindow(), getParentPath() ); | |
| 357 | } | |
| 358 | ||
| 359 | private void insertObject( final Dialog<String> dialog ) { | |
| 360 | dialog.showAndWait().ifPresent( | |
| 361 | result -> getEditor().replaceSelection( result ) | |
| 362 | ); | |
| 363 | } | |
| 364 | ||
| 365 | private Window getWindow() { | |
| 366 | return getScrollPane().getScene().getWindow(); | |
| 367 | } | |
| 368 | } | |
| 1 | 369 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.predicates; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.util.Collection; | |
| 32 | import java.util.function.Predicate; | |
| 33 | ||
| 34 | import static java.lang.String.join; | |
| 35 | import static java.nio.file.FileSystems.getDefault; | |
| 36 | ||
| 37 | /** | |
| 38 | * Provides a number of simple {@link Predicate} instances for various types | |
| 39 | * of string comparisons, including basic strings and file name strings. | |
| 40 | */ | |
| 41 | public class PredicateFactory { | |
| 42 | /** | |
| 43 | * Creates an instance of {@link Predicate} that matches a globbed file | |
| 44 | * name pattern. | |
| 45 | * | |
| 46 | * @param pattern The file name pattern to match. | |
| 47 | * @return A {@link Predicate} that can answer whether a given file name | |
| 48 | * matches the given glob pattern. | |
| 49 | */ | |
| 50 | public static Predicate<File> createFileTypePredicate( | |
| 51 | final String pattern ) { | |
| 52 | final var matcher = getDefault().getPathMatcher( | |
| 53 | "glob:**{" + pattern + "}" | |
| 54 | ); | |
| 55 | ||
| 56 | return file -> matcher.matches( file.toPath() ); | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * Creates an instance of {@link Predicate} that matches any file name from | |
| 61 | * a {@link Collection} of file name patterns. The given patterns are joined | |
| 62 | * with commas into a single comma-separated list. | |
| 63 | * | |
| 64 | * @param patterns The file name patterns to be matched. | |
| 65 | * @return A {@link Predicate} that can answer whether a given file name | |
| 66 | * matches the given glob patterns. | |
| 67 | */ | |
| 68 | public static Predicate<File> createFileTypePredicate( | |
| 69 | final Collection<String> patterns ) { | |
| 70 | return createFileTypePredicate( join( ",", patterns ) ); | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Creates an instance of {@link Predicate} that compares whether the given | |
| 75 | * {@code reference} string is contained by the comparator. Comparison is | |
| 76 | * case-insensitive. The test will also pass if the comparate is empty. | |
| 77 | * | |
| 78 | * @param comparator The string to check as being contained. | |
| 79 | * @return A {@link Predicate} that can answer whether the given string | |
| 80 | * is contained within the comparator, or the comparate is empty. | |
| 81 | */ | |
| 82 | public static Predicate<String> createStringContainsPredicate( | |
| 83 | final String comparator ) { | |
| 84 | return comparate -> comparate.isEmpty() || | |
| 85 | comparate.toLowerCase().contains( comparator.toLowerCase() ); | |
| 86 | } | |
| 1 | 87 | |
| 88 | /** | |
| 89 | * Creates an instance of {@link Predicate} that compares whether the given | |
| 90 | * {@code reference} string is starts with the comparator. Comparison is | |
| 91 | * case-insensitive. | |
| 92 | * | |
| 93 | * @param comparator The string to check as being contained. | |
| 94 | * @return A {@link Predicate} that can answer whether the given string | |
| 95 | * is contained within the comparator. | |
| 96 | */ | |
| 97 | public static Predicate<String> createStringStartsPredicate( | |
| 98 | final String comparator ) { | |
| 99 | return comparate -> | |
| 100 | comparate.toLowerCase().startsWith( comparator.toLowerCase() ); | |
| 101 | } | |
| 102 | } |
| 1 | /* | |
| 2 | * Copyright 2016 David Croft and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preferences; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.io.FileInputStream; | |
| 32 | import java.io.FileOutputStream; | |
| 33 | import java.util.*; | |
| 34 | import java.util.prefs.AbstractPreferences; | |
| 35 | import java.util.prefs.BackingStoreException; | |
| 36 | ||
| 37 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 38 | ||
| 39 | /** | |
| 40 | * Preferences implementation that stores to a user-defined file. Local file | |
| 41 | * storage is preferred over a certain operating system's monolithic trash heap | |
| 42 | * called a registry. When the OS is locked down, the default Preferences | |
| 43 | * implementation will try to write to the registry and fail due to permissions | |
| 44 | * problems. This class sidesteps the issue entirely by writing to the user's | |
| 45 | * home directory, where permissions should be a bit more lax. | |
| 46 | */ | |
| 47 | public class FilePreferences extends AbstractPreferences { | |
| 48 | ||
| 49 | private final Map<String, String> mRoot = new TreeMap<>(); | |
| 50 | private final Map<String, FilePreferences> mChildren = new TreeMap<>(); | |
| 51 | private boolean mRemoved; | |
| 52 | ||
| 53 | private final Object mMutex = new Object(); | |
| 54 | ||
| 55 | public FilePreferences( | |
| 56 | final AbstractPreferences parent, final String name ) { | |
| 57 | super( parent, name ); | |
| 58 | ||
| 59 | try { | |
| 60 | sync(); | |
| 61 | } catch( final BackingStoreException ex ) { | |
| 62 | alert( ex ); | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | @Override | |
| 67 | protected void putSpi( final String key, final String value ) { | |
| 68 | synchronized( mMutex ) { | |
| 69 | mRoot.put( key, value ); | |
| 70 | } | |
| 71 | ||
| 72 | try { | |
| 73 | flush(); | |
| 74 | } catch( final BackingStoreException ex ) { | |
| 75 | alert( ex ); | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | protected String getSpi( final String key ) { | |
| 81 | synchronized( mMutex ) { | |
| 82 | return mRoot.get( key ); | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | @Override | |
| 87 | protected void removeSpi( final String key ) { | |
| 88 | synchronized( mMutex ) { | |
| 89 | mRoot.remove( key ); | |
| 90 | } | |
| 91 | ||
| 92 | try { | |
| 93 | flush(); | |
| 94 | } catch( final BackingStoreException ex ) { | |
| 95 | alert( ex ); | |
| 96 | } | |
| 97 | } | |
| 98 | ||
| 99 | @Override | |
| 100 | protected void removeNodeSpi() throws BackingStoreException { | |
| 101 | mRemoved = true; | |
| 102 | flush(); | |
| 103 | } | |
| 104 | ||
| 105 | @Override | |
| 106 | protected String[] keysSpi() { | |
| 107 | synchronized( mMutex ) { | |
| 108 | return mRoot.keySet().toArray( new String[ 0 ] ); | |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | @Override | |
| 113 | protected String[] childrenNamesSpi() { | |
| 114 | return mChildren.keySet().toArray( new String[ 0 ] ); | |
| 115 | } | |
| 116 | ||
| 117 | @Override | |
| 118 | protected FilePreferences childSpi( final String name ) { | |
| 119 | FilePreferences child = mChildren.get( name ); | |
| 120 | ||
| 121 | if( child == null || child.isRemoved() ) { | |
| 122 | child = new FilePreferences( this, name ); | |
| 123 | mChildren.put( name, child ); | |
| 124 | } | |
| 125 | ||
| 126 | return child; | |
| 127 | } | |
| 128 | ||
| 129 | @Override | |
| 130 | protected void syncSpi() { | |
| 131 | if( isRemoved() ) { | |
| 132 | return; | |
| 133 | } | |
| 134 | ||
| 135 | final File file = FilePreferencesFactory.getPreferencesFile(); | |
| 136 | ||
| 137 | if( !file.exists() ) { | |
| 138 | return; | |
| 139 | } | |
| 140 | ||
| 141 | synchronized( mMutex ) { | |
| 142 | final Properties p = new Properties(); | |
| 143 | ||
| 144 | try( final var inputStream = new FileInputStream( file ) ) { | |
| 145 | p.load( inputStream ); | |
| 146 | ||
| 147 | final String path = getPath(); | |
| 148 | final Enumeration<?> propertyNames = p.propertyNames(); | |
| 149 | ||
| 150 | while( propertyNames.hasMoreElements() ) { | |
| 151 | final String propKey = (String) propertyNames.nextElement(); | |
| 152 | ||
| 153 | if( propKey.startsWith( path ) ) { | |
| 154 | final String subKey = propKey.substring( path.length() ); | |
| 155 | ||
| 156 | // Only load immediate descendants | |
| 157 | if( subKey.indexOf( '.' ) == -1 ) { | |
| 158 | mRoot.put( subKey, p.getProperty( propKey ) ); | |
| 159 | } | |
| 160 | } | |
| 161 | } | |
| 162 | } catch( final Exception ex ) { | |
| 163 | alert( ex ); | |
| 164 | } | |
| 165 | } | |
| 166 | } | |
| 167 | ||
| 168 | private String getPath() { | |
| 169 | final FilePreferences parent = (FilePreferences) parent(); | |
| 170 | ||
| 171 | return parent == null ? "" : parent.getPath() + name() + '.'; | |
| 172 | } | |
| 173 | ||
| 174 | @Override | |
| 175 | protected void flushSpi() { | |
| 176 | final File file = FilePreferencesFactory.getPreferencesFile(); | |
| 177 | ||
| 178 | synchronized( mMutex ) { | |
| 179 | final Properties p = new Properties(); | |
| 180 | ||
| 181 | try { | |
| 182 | final String path = getPath(); | |
| 183 | ||
| 184 | if( file.exists() ) { | |
| 185 | try( final var fis = new FileInputStream( file ) ) { | |
| 186 | p.load( fis ); | |
| 187 | } | |
| 188 | ||
| 189 | final List<String> toRemove = new ArrayList<>(); | |
| 190 | ||
| 191 | // Make a list of all direct children of this node to be removed | |
| 192 | final Enumeration<?> propertyNames = p.propertyNames(); | |
| 193 | ||
| 194 | while( propertyNames.hasMoreElements() ) { | |
| 195 | final String propKey = (String) propertyNames.nextElement(); | |
| 196 | if( propKey.startsWith( path ) ) { | |
| 197 | final String subKey = propKey.substring( path.length() ); | |
| 198 | ||
| 199 | // Only do immediate descendants | |
| 200 | if( subKey.indexOf( '.' ) == -1 ) { | |
| 201 | toRemove.add( propKey ); | |
| 202 | } | |
| 203 | } | |
| 204 | } | |
| 205 | ||
| 206 | // Remove them now that the enumeration is done with | |
| 207 | for( final String propKey : toRemove ) { | |
| 208 | p.remove( propKey ); | |
| 209 | } | |
| 210 | } | |
| 211 | ||
| 212 | // If this node hasn't been removed, add back in any values | |
| 213 | if( !mRemoved ) { | |
| 214 | for( final String s : mRoot.keySet() ) { | |
| 215 | p.setProperty( path + s, mRoot.get( s ) ); | |
| 216 | } | |
| 217 | } | |
| 218 | ||
| 219 | try( final var fos = new FileOutputStream( file ) ) { | |
| 220 | p.store( fos, "FilePreferences" ); | |
| 221 | } | |
| 222 | } catch( final Exception ex ) { | |
| 223 | alert( ex ); | |
| 224 | } | |
| 225 | } | |
| 226 | } | |
| 227 | } | |
| 1 | 228 |
| 1 | /* | |
| 2 | * Copyright 2016 David Croft and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preferences; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.nio.file.FileSystems; | |
| 32 | import java.util.prefs.Preferences; | |
| 33 | import java.util.prefs.PreferencesFactory; | |
| 34 | ||
| 35 | import static com.scrivenvar.Constants.APP_TITLE; | |
| 36 | ||
| 37 | /** | |
| 38 | * PreferencesFactory implementation that stores the preferences in a | |
| 39 | * user-defined file. Usage: | |
| 40 | * <pre> | |
| 41 | * System.setProperty( "java.util.prefs.PreferencesFactory", | |
| 42 | * FilePreferencesFactory.class.getName() ); | |
| 43 | * </pre> | |
| 44 | * <p> | |
| 45 | * The file defaults to <code>$user.home/.scrivenvar</code>, but can be changed | |
| 46 | * using <code>-Dapplication.name=preferences</code> when running the | |
| 47 | * application, or by calling <code>System.setProperty</code> with the | |
| 48 | * "application.name" property. | |
| 49 | * </p> | |
| 50 | */ | |
| 51 | public class FilePreferencesFactory implements PreferencesFactory { | |
| 52 | ||
| 53 | private static File preferencesFile; | |
| 54 | private Preferences rootPreferences; | |
| 55 | ||
| 56 | @Override | |
| 57 | public Preferences systemRoot() { | |
| 58 | return userRoot(); | |
| 59 | } | |
| 60 | ||
| 61 | @Override | |
| 62 | public synchronized Preferences userRoot() { | |
| 63 | if( rootPreferences == null ) { | |
| 64 | rootPreferences = new FilePreferences( null, "" ); | |
| 65 | } | |
| 66 | ||
| 67 | return rootPreferences; | |
| 68 | } | |
| 69 | ||
| 70 | public synchronized static File getPreferencesFile() { | |
| 71 | if( preferencesFile == null ) { | |
| 72 | String prefsFile = getPreferencesFilename(); | |
| 73 | ||
| 74 | preferencesFile = new File( prefsFile ).getAbsoluteFile(); | |
| 75 | } | |
| 76 | ||
| 77 | return preferencesFile; | |
| 78 | } | |
| 79 | ||
| 80 | public static String getPreferencesFilename() { | |
| 81 | final String filename = System.getProperty( "application.name", APP_TITLE ); | |
| 82 | return System.getProperty( "user.home" ) + getSeparator() + "." + filename; | |
| 83 | } | |
| 84 | ||
| 85 | public static String getSeparator() { | |
| 86 | return FileSystems.getDefault().getSeparator(); | |
| 87 | } | |
| 88 | } | |
| 1 | 89 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preferences; | |
| 29 | ||
| 30 | import com.dlsc.formsfx.model.structure.StringField; | |
| 31 | import com.dlsc.preferencesfx.PreferencesFx; | |
| 32 | import com.dlsc.preferencesfx.PreferencesFxEvent; | |
| 33 | import com.dlsc.preferencesfx.model.Category; | |
| 34 | import com.dlsc.preferencesfx.model.Group; | |
| 35 | import com.dlsc.preferencesfx.model.Setting; | |
| 36 | import javafx.beans.property.*; | |
| 37 | import javafx.event.EventHandler; | |
| 38 | import javafx.scene.Node; | |
| 39 | import javafx.scene.control.Label; | |
| 40 | ||
| 41 | import java.io.File; | |
| 42 | import java.nio.file.Path; | |
| 43 | ||
| 44 | import static com.scrivenvar.Constants.*; | |
| 45 | import static com.scrivenvar.Messages.get; | |
| 46 | ||
| 47 | /** | |
| 48 | * Responsible for user preferences that can be changed from the GUI. The | |
| 49 | * settings are displayed and persisted using {@link PreferencesFx}. | |
| 50 | */ | |
| 51 | public class UserPreferences { | |
| 52 | private final PreferencesFx mPreferencesFx; | |
| 53 | ||
| 54 | private final ObjectProperty<File> mPropRDirectory; | |
| 55 | private final StringProperty mPropRScript; | |
| 56 | private final ObjectProperty<File> mPropImagesDirectory; | |
| 57 | private final StringProperty mPropImagesOrder; | |
| 58 | private final ObjectProperty<File> mPropDefinitionPath; | |
| 59 | private final StringProperty mRDelimiterBegan; | |
| 60 | private final StringProperty mRDelimiterEnded; | |
| 61 | private final StringProperty mDefDelimiterBegan; | |
| 62 | private final StringProperty mDefDelimiterEnded; | |
| 63 | private final IntegerProperty mPropFontsSizeEditor; | |
| 64 | ||
| 65 | public UserPreferences() { | |
| 66 | mPropRDirectory = simpleFile( USER_DIRECTORY ); | |
| 67 | mPropRScript = new SimpleStringProperty( "" ); | |
| 68 | ||
| 69 | mPropImagesDirectory = simpleFile( USER_DIRECTORY ); | |
| 70 | mPropImagesOrder = new SimpleStringProperty( PERSIST_IMAGES_DEFAULT ); | |
| 71 | ||
| 72 | mPropDefinitionPath = simpleFile( | |
| 73 | getSetting( "file.definition.default", DEFINITION_NAME ) | |
| 74 | ); | |
| 75 | ||
| 76 | mDefDelimiterBegan = new SimpleStringProperty( DEF_DELIM_BEGAN_DEFAULT ); | |
| 77 | mDefDelimiterEnded = new SimpleStringProperty( DEF_DELIM_ENDED_DEFAULT ); | |
| 78 | ||
| 79 | mRDelimiterBegan = new SimpleStringProperty( R_DELIM_BEGAN_DEFAULT ); | |
| 80 | mRDelimiterEnded = new SimpleStringProperty( R_DELIM_ENDED_DEFAULT ); | |
| 81 | ||
| 82 | mPropFontsSizeEditor = new SimpleIntegerProperty( FONT_SIZE_EDITOR ); | |
| 83 | ||
| 84 | // All properties must be initialized before creating the dialog. | |
| 85 | mPreferencesFx = createPreferencesFx(); | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Display the user preferences settings dialog (non-modal). | |
| 90 | */ | |
| 91 | public void show() { | |
| 92 | getPreferencesFx().show( false ); | |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * Call to persist the settings. Strictly speaking, this could watch on | |
| 97 | * all values for external changes then save automatically. | |
| 98 | */ | |
| 99 | public void save() { | |
| 100 | getPreferencesFx().saveSettings(); | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Creates the preferences dialog. | |
| 105 | * <p> | |
| 106 | * TODO: Make this dynamic by iterating over all "Preferences.*" values | |
| 107 | * that follow a particular naming pattern. | |
| 108 | * </p> | |
| 109 | * | |
| 110 | * @return A new instance of preferences for users to edit. | |
| 111 | */ | |
| 112 | @SuppressWarnings("unchecked") | |
| 113 | private PreferencesFx createPreferencesFx() { | |
| 114 | final Setting<StringField, StringProperty> scriptSetting = | |
| 115 | Setting.of( "Script", mPropRScript ); | |
| 116 | final StringField field = scriptSetting.getElement(); | |
| 117 | field.multiline( true ); | |
| 118 | ||
| 119 | return PreferencesFx.of( | |
| 120 | UserPreferences.class, | |
| 121 | Category.of( | |
| 122 | get( "Preferences.r" ), | |
| 123 | Group.of( | |
| 124 | get( "Preferences.r.directory" ), | |
| 125 | Setting.of( label( "Preferences.r.directory.desc", false ) ), | |
| 126 | Setting.of( "Directory", mPropRDirectory, true ) | |
| 127 | ), | |
| 128 | Group.of( | |
| 129 | get( "Preferences.r.script" ), | |
| 130 | Setting.of( label( "Preferences.r.script.desc" ) ), | |
| 131 | scriptSetting | |
| 132 | ), | |
| 133 | Group.of( | |
| 134 | get( "Preferences.r.delimiter.began" ), | |
| 135 | Setting.of( label( "Preferences.r.delimiter.began.desc" ) ), | |
| 136 | Setting.of( "Opening", mRDelimiterBegan ) | |
| 137 | ), | |
| 138 | Group.of( | |
| 139 | get( "Preferences.r.delimiter.ended" ), | |
| 140 | Setting.of( label( "Preferences.r.delimiter.ended.desc" ) ), | |
| 141 | Setting.of( "Closing", mRDelimiterEnded ) | |
| 142 | ) | |
| 143 | ), | |
| 144 | Category.of( | |
| 145 | get( "Preferences.images" ), | |
| 146 | Group.of( | |
| 147 | get( "Preferences.images.directory" ), | |
| 148 | Setting.of( label( "Preferences.images.directory.desc" ) ), | |
| 149 | Setting.of( "Directory", mPropImagesDirectory, true ) | |
| 150 | ), | |
| 151 | Group.of( | |
| 152 | get( "Preferences.images.suffixes" ), | |
| 153 | Setting.of( label( "Preferences.images.suffixes.desc" ) ), | |
| 154 | Setting.of( "Extensions", mPropImagesOrder ) | |
| 155 | ) | |
| 156 | ), | |
| 157 | Category.of( | |
| 158 | get( "Preferences.definitions" ), | |
| 159 | Group.of( | |
| 160 | get( "Preferences.definitions.path" ), | |
| 161 | Setting.of( label( "Preferences.definitions.path.desc" ) ), | |
| 162 | Setting.of( "Path", mPropDefinitionPath, false ) | |
| 163 | ), | |
| 164 | Group.of( | |
| 165 | get( "Preferences.definitions.delimiter.began" ), | |
| 166 | Setting.of( label( "Preferences.definitions.delimiter.began.desc" ) ), | |
| 167 | Setting.of( "Opening", mDefDelimiterBegan ) | |
| 168 | ), | |
| 169 | Group.of( | |
| 170 | get( "Preferences.definitions.delimiter.ended" ), | |
| 171 | Setting.of( label( "Preferences.definitions.delimiter.ended.desc" ) ), | |
| 172 | Setting.of( "Closing", mDefDelimiterEnded ) | |
| 173 | ) | |
| 174 | ), | |
| 175 | Category.of( | |
| 176 | get( "Preferences.fonts" ), | |
| 177 | Group.of( | |
| 178 | get( "Preferences.fonts.size_editor" ), | |
| 179 | Setting.of( label( "Preferences.fonts.size_editor.desc" ) ), | |
| 180 | Setting.of( "Points", mPropFontsSizeEditor ) | |
| 181 | ) | |
| 182 | ) | |
| 183 | ).instantPersistent( false ); | |
| 184 | } | |
| 185 | ||
| 186 | /** | |
| 187 | * Wraps a {@link File} inside a {@link SimpleObjectProperty}. | |
| 188 | * | |
| 189 | * @param path The file name to use when constructing the {@link File}. | |
| 190 | * @return A new {@link SimpleObjectProperty} instance with a {@link File} | |
| 191 | * that references the given {@code path}. | |
| 192 | */ | |
| 193 | private SimpleObjectProperty<File> simpleFile( final String path ) { | |
| 194 | return new SimpleObjectProperty<>( new File( path ) ); | |
| 195 | } | |
| 196 | ||
| 197 | /** | |
| 198 | * Creates a label for the given key after interpolating its value. | |
| 199 | * | |
| 200 | * @param key The key to find in the resource bundle. | |
| 201 | * @return The value of the key as a label. | |
| 202 | */ | |
| 203 | private Node label( final String key ) { | |
| 204 | return new Label( get( key, true ) ); | |
| 205 | } | |
| 206 | ||
| 207 | /** | |
| 208 | * Creates a label for the given key. | |
| 209 | * | |
| 210 | * @param key The key to find in the resource bundle. | |
| 211 | * @param interpolate {@code true} means to interpolate the value. | |
| 212 | * @return The value of the key, interpolated if {@code interpolate} is | |
| 213 | * {@code true}. | |
| 214 | */ | |
| 215 | @SuppressWarnings("SameParameterValue") | |
| 216 | private Node label( final String key, final boolean interpolate ) { | |
| 217 | return new Label( get( key, interpolate ) ); | |
| 218 | } | |
| 219 | ||
| 220 | /** | |
| 221 | * Delegates to the {@link PreferencesFx} event handler for monitoring | |
| 222 | * save events. | |
| 223 | * | |
| 224 | * @param eventHandler The handler to call when the preferences are saved. | |
| 225 | */ | |
| 226 | public void addSaveEventHandler( | |
| 227 | final EventHandler<? super PreferencesFxEvent> eventHandler ) { | |
| 228 | final var eventType = PreferencesFxEvent.EVENT_PREFERENCES_SAVED; | |
| 229 | getPreferencesFx().addEventHandler( eventType, eventHandler ); | |
| 230 | } | |
| 231 | ||
| 232 | /** | |
| 233 | * Returns the value for a key from the settings properties file. | |
| 234 | * | |
| 235 | * @param key Key within the settings properties file to find. | |
| 236 | * @param value Default value to return if the key is not found. | |
| 237 | * @return The value for the given key from the settings file, or the | |
| 238 | * given {@code value} if no key found. | |
| 239 | */ | |
| 240 | @SuppressWarnings("SameParameterValue") | |
| 241 | private String getSetting( final String key, final String value ) { | |
| 242 | return SETTINGS.getSetting( key, value ); | |
| 243 | } | |
| 244 | ||
| 245 | public ObjectProperty<File> definitionPathProperty() { | |
| 246 | return mPropDefinitionPath; | |
| 247 | } | |
| 248 | ||
| 249 | public Path getDefinitionPath() { | |
| 250 | return definitionPathProperty().getValue().toPath(); | |
| 251 | } | |
| 252 | ||
| 253 | private StringProperty defDelimiterBegan() { | |
| 254 | return mDefDelimiterBegan; | |
| 255 | } | |
| 256 | ||
| 257 | public String getDefDelimiterBegan() { | |
| 258 | return defDelimiterBegan().get(); | |
| 259 | } | |
| 260 | ||
| 261 | private StringProperty defDelimiterEnded() { | |
| 262 | return mDefDelimiterEnded; | |
| 263 | } | |
| 264 | ||
| 265 | public String getDefDelimiterEnded() { | |
| 266 | return defDelimiterEnded().get(); | |
| 267 | } | |
| 268 | ||
| 269 | public ObjectProperty<File> rDirectoryProperty() { | |
| 270 | return mPropRDirectory; | |
| 271 | } | |
| 272 | ||
| 273 | public File getRDirectory() { | |
| 274 | return rDirectoryProperty().getValue(); | |
| 275 | } | |
| 276 | ||
| 277 | public StringProperty rScriptProperty() { | |
| 278 | return mPropRScript; | |
| 279 | } | |
| 280 | ||
| 281 | public String getRScript() { | |
| 282 | return rScriptProperty().getValue(); | |
| 283 | } | |
| 284 | ||
| 285 | private StringProperty rDelimiterBegan() { | |
| 286 | return mRDelimiterBegan; | |
| 287 | } | |
| 288 | ||
| 289 | public String getRDelimiterBegan() { | |
| 290 | return rDelimiterBegan().get(); | |
| 291 | } | |
| 292 | ||
| 293 | private StringProperty rDelimiterEnded() { | |
| 294 | return mRDelimiterEnded; | |
| 295 | } | |
| 296 | ||
| 297 | public String getRDelimiterEnded() { | |
| 298 | return rDelimiterEnded().get(); | |
| 299 | } | |
| 300 | ||
| 301 | private ObjectProperty<File> imagesDirectoryProperty() { | |
| 302 | return mPropImagesDirectory; | |
| 303 | } | |
| 304 | ||
| 305 | public File getImagesDirectory() { | |
| 306 | return imagesDirectoryProperty().getValue(); | |
| 307 | } | |
| 308 | ||
| 309 | private StringProperty imagesOrderProperty() { | |
| 310 | return mPropImagesOrder; | |
| 311 | } | |
| 312 | ||
| 313 | public String getImagesOrder() { | |
| 314 | return imagesOrderProperty().getValue(); | |
| 315 | } | |
| 316 | ||
| 317 | public IntegerProperty fontsSizeEditorProperty() { | |
| 318 | return mPropFontsSizeEditor; | |
| 319 | } | |
| 320 | ||
| 321 | /** | |
| 322 | * Returns the preferred font size of the text editor. | |
| 323 | * | |
| 324 | * @return A non-negative integer, in points. | |
| 325 | */ | |
| 326 | public int getFontsSizeEditor() { | |
| 327 | return mPropFontsSizeEditor.intValue(); | |
| 328 | } | |
| 329 | ||
| 330 | private PreferencesFx getPreferencesFx() { | |
| 331 | return mPreferencesFx; | |
| 332 | } | |
| 333 | } | |
| 1 | 334 |
| 1 | /* | |
| 2 | * Copyright 2006 Patrick Wright | |
| 3 | * Copyright 2007 Wisconsin Court System | |
| 4 | * Copyright 2020 White Magic Software, Ltd. | |
| 5 | * | |
| 6 | * This program is free software; you can redistribute it and/or | |
| 7 | * modify it under the terms of the GNU Lesser General Public License | |
| 8 | * as published by the Free Software Foundation; either version 2.1 | |
| 9 | * of the License, or (at your option) any later version. | |
| 10 | * | |
| 11 | * This program is distributed in the hope that it will be useful, | |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 14 | * GNU Lesser General Public License for more details. | |
| 15 | * | |
| 16 | * You should have received a copy of the GNU Lesser General Public License | |
| 17 | * along with this program; if not, write to the Free Software | |
| 18 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| 19 | */ | |
| 20 | package com.scrivenvar.preview; | |
| 21 | ||
| 22 | import com.scrivenvar.adapters.ReplacedElementAdapter; | |
| 23 | import org.w3c.dom.Element; | |
| 24 | import org.xhtmlrenderer.extend.ReplacedElement; | |
| 25 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 26 | import org.xhtmlrenderer.extend.UserAgentCallback; | |
| 27 | import org.xhtmlrenderer.layout.LayoutContext; | |
| 28 | import org.xhtmlrenderer.render.BlockBox; | |
| 29 | ||
| 30 | import java.util.HashSet; | |
| 31 | import java.util.Set; | |
| 32 | ||
| 33 | public class ChainedReplacedElementFactory extends ReplacedElementAdapter { | |
| 34 | private final Set<ReplacedElementFactory> mFactoryList = new HashSet<>(); | |
| 35 | ||
| 36 | @Override | |
| 37 | public ReplacedElement createReplacedElement( | |
| 38 | final LayoutContext c, | |
| 39 | final BlockBox box, | |
| 40 | final UserAgentCallback uac, | |
| 41 | final int cssWidth, | |
| 42 | final int cssHeight ) { | |
| 43 | for( final var f : mFactoryList ) { | |
| 44 | final var r = f.createReplacedElement( | |
| 45 | c, box, uac, cssWidth, cssHeight ); | |
| 46 | ||
| 47 | if( r != null ) { | |
| 48 | return r; | |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | return null; | |
| 53 | } | |
| 54 | ||
| 55 | @Override | |
| 56 | public void reset() { | |
| 57 | for( final var factory : mFactoryList ) { | |
| 58 | factory.reset(); | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public void remove( final Element element ) { | |
| 64 | for( final var factory : mFactoryList ) { | |
| 65 | factory.remove( element ); | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | public void addFactory( final ReplacedElementFactory factory ) { | |
| 70 | mFactoryList.add( factory ); | |
| 71 | } | |
| 72 | } | |
| 1 | 73 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import javafx.beans.property.IntegerProperty; | |
| 31 | import javafx.beans.property.SimpleIntegerProperty; | |
| 32 | import org.xhtmlrenderer.extend.FSImage; | |
| 33 | import org.xhtmlrenderer.resource.ImageResource; | |
| 34 | import org.xhtmlrenderer.swing.ImageResourceLoader; | |
| 35 | ||
| 36 | import java.net.URI; | |
| 37 | import java.nio.file.Files; | |
| 38 | import java.nio.file.Paths; | |
| 39 | ||
| 40 | import static com.scrivenvar.preview.SvgRasterizer.BROKEN_IMAGE_PLACEHOLDER; | |
| 41 | import static com.scrivenvar.util.ProtocolResolver.getProtocol; | |
| 42 | import static org.xhtmlrenderer.swing.AWTFSImage.createImage; | |
| 43 | ||
| 44 | /** | |
| 45 | * Responsible for loading images. If the image cannot be found, a placeholder | |
| 46 | * is used instead. | |
| 47 | */ | |
| 48 | public class CustomImageLoader extends ImageResourceLoader { | |
| 49 | /** | |
| 50 | * Placeholder that's displayed when image cannot be found. | |
| 51 | */ | |
| 52 | private static final FSImage BROKEN_IMAGE = createImage( | |
| 53 | BROKEN_IMAGE_PLACEHOLDER ); | |
| 54 | ||
| 55 | private final IntegerProperty mWidthProperty = new SimpleIntegerProperty(); | |
| 56 | ||
| 57 | /** | |
| 58 | * Gets an {@link IntegerProperty} that represents the maximum width an | |
| 59 | * image should be scaled. | |
| 60 | * | |
| 61 | * @return The maximum width for an image. | |
| 62 | */ | |
| 63 | public IntegerProperty widthProperty() { | |
| 64 | return mWidthProperty; | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Gets an image resolved from the given URI. If the image cannot be found, | |
| 69 | * this will return a custom placeholder image indicating the reference | |
| 70 | * is broken. | |
| 71 | * | |
| 72 | * @param uri Path to the image resource to load. | |
| 73 | * @param width Ignored. | |
| 74 | * @param height Ignored. | |
| 75 | * @return The scaled image, or a placeholder image if the URI's content | |
| 76 | * could not be retrieved. | |
| 77 | */ | |
| 78 | @Override | |
| 79 | public synchronized ImageResource get( | |
| 80 | final String uri, final int width, final int height ) { | |
| 81 | assert uri != null; | |
| 82 | assert width >= 0; | |
| 83 | assert height >= 0; | |
| 84 | ||
| 85 | boolean exists = true; | |
| 86 | ||
| 87 | try { | |
| 88 | final var protocol = getProtocol( uri ); | |
| 89 | ||
| 90 | if( protocol.isFile() ) { | |
| 91 | exists = Files.exists( Paths.get( new URI( uri ) ) ); | |
| 92 | } | |
| 93 | } catch( final Exception e ) { | |
| 94 | exists = false; | |
| 95 | } | |
| 96 | ||
| 97 | return exists | |
| 98 | ? scale( uri, width, height ) | |
| 99 | : new ImageResource( uri, BROKEN_IMAGE ); | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Scales the image found at the given URI. | |
| 104 | * | |
| 105 | * @param uri Path to the image file to load. | |
| 106 | * @param w Ignored. | |
| 107 | * @param h Ignored. | |
| 108 | * @return Resource representing the rendered image and path. | |
| 109 | */ | |
| 110 | private ImageResource scale( final String uri, final int w, final int h ) { | |
| 111 | final var ir = super.get( uri, w, h ); | |
| 112 | final var image = ir.getImage(); | |
| 113 | final var imageWidth = image.getWidth(); | |
| 114 | final var imageHeight = image.getHeight(); | |
| 115 | ||
| 116 | int maxWidth = mWidthProperty.get(); | |
| 117 | int newWidth = imageWidth; | |
| 118 | int newHeight = imageHeight; | |
| 119 | ||
| 120 | // Maintain aspect ratio while shrinking image to view port bounds. | |
| 121 | if( imageWidth > maxWidth ) { | |
| 122 | newWidth = maxWidth; | |
| 123 | newHeight = (newWidth * imageHeight) / imageWidth; | |
| 124 | } | |
| 125 | ||
| 126 | image.scale( newWidth, newHeight ); | |
| 127 | return ir; | |
| 128 | } | |
| 129 | } | |
| 1 | 130 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import com.scrivenvar.adapters.DocumentAdapter; | |
| 31 | import javafx.beans.property.BooleanProperty; | |
| 32 | import javafx.beans.property.SimpleBooleanProperty; | |
| 33 | import javafx.beans.value.ChangeListener; | |
| 34 | import javafx.beans.value.ObservableValue; | |
| 35 | import javafx.embed.swing.SwingNode; | |
| 36 | import javafx.scene.Node; | |
| 37 | import org.jsoup.Jsoup; | |
| 38 | import org.jsoup.helper.W3CDom; | |
| 39 | import org.jsoup.nodes.Document; | |
| 40 | import org.xhtmlrenderer.layout.SharedContext; | |
| 41 | import org.xhtmlrenderer.render.Box; | |
| 42 | import org.xhtmlrenderer.simple.XHTMLPanel; | |
| 43 | import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; | |
| 44 | import org.xhtmlrenderer.swing.*; | |
| 45 | ||
| 46 | import javax.swing.*; | |
| 47 | import java.awt.*; | |
| 48 | import java.awt.event.ComponentAdapter; | |
| 49 | import java.awt.event.ComponentEvent; | |
| 50 | import java.net.URI; | |
| 51 | import java.nio.file.Path; | |
| 52 | ||
| 53 | import static com.scrivenvar.Constants.*; | |
| 54 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 55 | import static com.scrivenvar.util.ProtocolResolver.getProtocol; | |
| 56 | import static java.awt.Desktop.Action.BROWSE; | |
| 57 | import static java.awt.Desktop.getDesktop; | |
| 58 | import static java.lang.Math.max; | |
| 59 | import static javax.swing.SwingUtilities.invokeLater; | |
| 60 | import static org.xhtmlrenderer.swing.ImageResourceLoader.NO_OP_REPAINT_LISTENER; | |
| 61 | ||
| 62 | /** | |
| 63 | * HTML preview pane is responsible for rendering an HTML document. | |
| 64 | */ | |
| 65 | public final class HTMLPreviewPane extends SwingNode { | |
| 66 | ||
| 67 | /** | |
| 68 | * Suppresses scrolling to the top on every key press. | |
| 69 | */ | |
| 70 | private static class HTMLPanel extends XHTMLPanel { | |
| 71 | @Override | |
| 72 | public void resetScrollPosition() { | |
| 73 | } | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Suppresses scroll attempts until after the document has loaded. | |
| 78 | */ | |
| 79 | private static final class DocumentEventHandler extends DocumentAdapter { | |
| 80 | private final BooleanProperty mReadyProperty = new SimpleBooleanProperty(); | |
| 81 | ||
| 82 | public BooleanProperty readyProperty() { | |
| 83 | return mReadyProperty; | |
| 84 | } | |
| 85 | ||
| 86 | @Override | |
| 87 | public void documentStarted() { | |
| 88 | mReadyProperty.setValue( Boolean.FALSE ); | |
| 89 | } | |
| 90 | ||
| 91 | @Override | |
| 92 | public void documentLoaded() { | |
| 93 | mReadyProperty.setValue( Boolean.TRUE ); | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Ensure that images are constrained to the panel width upon resizing. | |
| 99 | */ | |
| 100 | private final class ResizeListener extends ComponentAdapter { | |
| 101 | @Override | |
| 102 | public void componentResized( final ComponentEvent e ) { | |
| 103 | setWidth( e ); | |
| 104 | } | |
| 105 | ||
| 106 | @Override | |
| 107 | public void componentShown( final ComponentEvent e ) { | |
| 108 | setWidth( e ); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Sets the width of the {@link HTMLPreviewPane} so that images can be | |
| 113 | * scaled to fit. The scale factor is adjusted a bit below the full width | |
| 114 | * to prevent the horizontal scrollbar from appearing. | |
| 115 | * | |
| 116 | * @param event The component that defines the image scaling width. | |
| 117 | */ | |
| 118 | private void setWidth( final ComponentEvent event ) { | |
| 119 | final int width = (int) (event.getComponent().getWidth() * .95); | |
| 120 | HTMLPreviewPane.this.mImageLoader.widthProperty().set( width ); | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | /** | |
| 125 | * Responsible for opening hyperlinks. External hyperlinks are opened in | |
| 126 | * the system's default browser; local file system links are opened in the | |
| 127 | * editor. | |
| 128 | */ | |
| 129 | private static class HyperlinkListener extends LinkListener { | |
| 130 | @Override | |
| 131 | public void linkClicked( final BasicPanel panel, final String link ) { | |
| 132 | try { | |
| 133 | final var protocol = getProtocol( link ); | |
| 134 | ||
| 135 | switch( protocol ) { | |
| 136 | case HTTP: | |
| 137 | final var desktop = getDesktop(); | |
| 138 | ||
| 139 | if( desktop.isSupported( BROWSE ) ) { | |
| 140 | desktop.browse( new URI( link ) ); | |
| 141 | } | |
| 142 | break; | |
| 143 | case FILE: | |
| 144 | // TODO: #88 -- publish a message to the event bus. | |
| 145 | break; | |
| 146 | } | |
| 147 | } catch( final Exception ex ) { | |
| 148 | alert( ex ); | |
| 149 | } | |
| 150 | } | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * The CSS must be rendered in points (pt) not pixels (px) to avoid blurry | |
| 155 | * rendering on some platforms. | |
| 156 | */ | |
| 157 | private static final String HTML_PREFIX = "<!DOCTYPE html>" | |
| 158 | + "<html>" | |
| 159 | + "<head>" | |
| 160 | + "<link rel='stylesheet' href='" + | |
| 161 | HTMLPreviewPane.class.getResource( STYLESHEET_PREVIEW ) + "'/>" | |
| 162 | + "</head>" | |
| 163 | + "<body>"; | |
| 164 | ||
| 165 | // Provide some extra space at the end for scrolling past the last line. | |
| 166 | private static final String HTML_SUFFIX = | |
| 167 | "<p style='height=2em'> </p></body></html>"; | |
| 168 | ||
| 169 | private static final W3CDom W3C_DOM = new W3CDom(); | |
| 170 | private static final XhtmlNamespaceHandler NS_HANDLER = | |
| 171 | new XhtmlNamespaceHandler(); | |
| 172 | ||
| 173 | private final StringBuilder mHtmlDocument = new StringBuilder( 65536 ); | |
| 174 | private final int mHtmlPrefixLength; | |
| 175 | ||
| 176 | private final HTMLPanel mHtmlRenderer = new HTMLPanel(); | |
| 177 | private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer ); | |
| 178 | private final DocumentEventHandler mDocHandler = new DocumentEventHandler(); | |
| 179 | private final CustomImageLoader mImageLoader = new CustomImageLoader(); | |
| 180 | ||
| 181 | private Path mPath = DEFAULT_DIRECTORY; | |
| 182 | ||
| 183 | /** | |
| 184 | * Creates a new preview pane that can scroll to the caret position within the | |
| 185 | * document. | |
| 186 | */ | |
| 187 | public HTMLPreviewPane() { | |
| 188 | setStyle( "-fx-background-color: white;" ); | |
| 189 | ||
| 190 | // No need to append same prefix each time the HTML content is updated. | |
| 191 | mHtmlDocument.append( HTML_PREFIX ); | |
| 192 | mHtmlPrefixLength = mHtmlDocument.length(); | |
| 193 | ||
| 194 | // Inject an SVG renderer that produces high-quality SVG buffered images. | |
| 195 | final var factory = new ChainedReplacedElementFactory(); | |
| 196 | factory.addFactory( new SvgReplacedElementFactory() ); | |
| 197 | factory.addFactory( new SwingReplacedElementFactory( | |
| 198 | NO_OP_REPAINT_LISTENER, mImageLoader ) ); | |
| 199 | ||
| 200 | final var context = getSharedContext(); | |
| 201 | final var textRenderer = context.getTextRenderer(); | |
| 202 | context.setReplacedElementFactory( factory ); | |
| 203 | textRenderer.setSmoothingThreshold( 0 ); | |
| 204 | ||
| 205 | setContent( mScrollPane ); | |
| 206 | mHtmlRenderer.addDocumentListener( mDocHandler ); | |
| 207 | mHtmlRenderer.addComponentListener( new ResizeListener() ); | |
| 208 | ||
| 209 | // The default mouse click listener attempts navigation within the | |
| 210 | // preview panel. We want to usurp that behaviour to open the link in | |
| 211 | // a platform-specific browser. | |
| 212 | for( final var listener : mHtmlRenderer.getMouseTrackingListeners() ) { | |
| 213 | if( !(listener instanceof HoverListener) ) { | |
| 214 | mHtmlRenderer.removeMouseTrackingListener( (FSMouseListener) listener ); | |
| 215 | } | |
| 216 | } | |
| 217 | ||
| 218 | mHtmlRenderer.addMouseTrackingListener( new HyperlinkListener() ); | |
| 219 | } | |
| 220 | ||
| 221 | /** | |
| 222 | * Updates the internal HTML source, loads it into the preview pane, then | |
| 223 | * scrolls to the caret position. | |
| 224 | * | |
| 225 | * @param html The new HTML document to display. | |
| 226 | */ | |
| 227 | public void process( final String html ) { | |
| 228 | final Document jsoupDoc = Jsoup.parse( decorate( html ) ); | |
| 229 | final org.w3c.dom.Document w3cDoc = W3C_DOM.fromJsoup( jsoupDoc ); | |
| 230 | ||
| 231 | ||
| 232 | // Access to a Swing component must occur from the Event Dispatch | |
| 233 | // thread according to Swing threading restrictions. | |
| 234 | invokeLater( | |
| 235 | () -> mHtmlRenderer.setDocument( w3cDoc, getBaseUrl(), NS_HANDLER ) | |
| 236 | ); | |
| 237 | } | |
| 238 | ||
| 239 | public void clear() { | |
| 240 | process( "" ); | |
| 241 | } | |
| 242 | ||
| 243 | /** | |
| 244 | * Scrolls to an anchor link. The anchor links are injected when the | |
| 245 | * HTML document is created. | |
| 246 | * | |
| 247 | * @param id The unique anchor link identifier. | |
| 248 | */ | |
| 249 | public void tryScrollTo( final int id ) { | |
| 250 | final ChangeListener<Boolean> listener = new ChangeListener<>() { | |
| 251 | @Override | |
| 252 | public void changed( | |
| 253 | final ObservableValue<? extends Boolean> observable, | |
| 254 | final Boolean oldValue, | |
| 255 | final Boolean newValue ) { | |
| 256 | if( newValue ) { | |
| 257 | scrollTo( id ); | |
| 258 | ||
| 259 | mDocHandler.readyProperty().removeListener( this ); | |
| 260 | } | |
| 261 | } | |
| 262 | }; | |
| 263 | ||
| 264 | mDocHandler.readyProperty().addListener( listener ); | |
| 265 | } | |
| 266 | ||
| 267 | /** | |
| 268 | * Scrolls to the closest element matching the given identifier without | |
| 269 | * waiting for the document to be ready. Be sure the document is ready | |
| 270 | * before calling this method. | |
| 271 | * | |
| 272 | * @param id Paragraph index. | |
| 273 | */ | |
| 274 | public void scrollTo( final int id ) { | |
| 275 | if( id < 2 ) { | |
| 276 | scrollToTop(); | |
| 277 | } | |
| 278 | else { | |
| 279 | Box box = findPrevBox( id ); | |
| 280 | box = box == null ? findNextBox( id + 1 ) : box; | |
| 281 | ||
| 282 | if( box == null ) { | |
| 283 | scrollToBottom(); | |
| 284 | } | |
| 285 | else { | |
| 286 | scrollTo( box ); | |
| 287 | } | |
| 288 | } | |
| 289 | } | |
| 290 | ||
| 291 | private Box findPrevBox( final int id ) { | |
| 292 | int prevId = id; | |
| 293 | Box box = null; | |
| 294 | ||
| 295 | while( prevId > 0 && (box = getBoxById( PARAGRAPH_ID_PREFIX + prevId )) == null ) { | |
| 296 | prevId--; | |
| 297 | } | |
| 298 | ||
| 299 | return box; | |
| 300 | } | |
| 301 | ||
| 302 | private Box findNextBox( final int id ) { | |
| 303 | int nextId = id; | |
| 304 | Box box = null; | |
| 305 | ||
| 306 | while( nextId - id < 5 && | |
| 307 | (box = getBoxById( PARAGRAPH_ID_PREFIX + nextId )) == null ) { | |
| 308 | nextId++; | |
| 309 | } | |
| 310 | ||
| 311 | return box; | |
| 312 | } | |
| 313 | ||
| 314 | private void scrollTo( final Point point ) { | |
| 315 | invokeLater( () -> mHtmlRenderer.scrollTo( point ) ); | |
| 316 | } | |
| 317 | ||
| 318 | private void scrollTo( final Box box ) { | |
| 319 | scrollTo( createPoint( box ) ); | |
| 320 | } | |
| 321 | ||
| 322 | private void scrollToY( final int y ) { | |
| 323 | scrollTo( new Point( 0, y ) ); | |
| 324 | } | |
| 325 | ||
| 326 | private void scrollToTop() { | |
| 327 | scrollToY( 0 ); | |
| 328 | } | |
| 329 | ||
| 330 | private void scrollToBottom() { | |
| 331 | scrollToY( mHtmlRenderer.getHeight() ); | |
| 332 | } | |
| 333 | ||
| 334 | private Box getBoxById( final String id ) { | |
| 335 | return getSharedContext().getBoxById( id ); | |
| 336 | } | |
| 337 | ||
| 338 | private String decorate( final String html ) { | |
| 339 | // Trim the HTML back to only the prefix. | |
| 340 | mHtmlDocument.setLength( mHtmlPrefixLength ); | |
| 341 | ||
| 342 | // Write the HTML body element followed by closing tags. | |
| 343 | return mHtmlDocument.append( html ).append( HTML_SUFFIX ).toString(); | |
| 344 | } | |
| 345 | ||
| 346 | public Path getPath() { | |
| 347 | return mPath; | |
| 348 | } | |
| 349 | ||
| 350 | public void setPath( final Path path ) { | |
| 351 | assert path != null; | |
| 352 | mPath = path; | |
| 353 | } | |
| 354 | ||
| 355 | /** | |
| 356 | * Content to embed in a panel. | |
| 357 | * | |
| 358 | * @return The content to display to the user. | |
| 359 | */ | |
| 360 | public Node getNode() { | |
| 361 | return this; | |
| 362 | } | |
| 363 | ||
| 364 | public JScrollPane getScrollPane() { | |
| 365 | return mScrollPane; | |
| 366 | } | |
| 367 | ||
| 368 | public JScrollBar getVerticalScrollBar() { | |
| 369 | return getScrollPane().getVerticalScrollBar(); | |
| 370 | } | |
| 371 | ||
| 372 | /** | |
| 373 | * Creates a {@link Point} to use as a reference for scrolling to the area | |
| 374 | * described by the given {@link Box}. The {@link Box} coordinates are used | |
| 375 | * to populate the {@link Point}'s location, with minor adjustments for | |
| 376 | * vertical centering. | |
| 377 | * | |
| 378 | * @param box The {@link Box} that represents a scrolling anchor reference. | |
| 379 | * @return A coordinate suitable for scrolling to. | |
| 380 | */ | |
| 381 | private Point createPoint( final Box box ) { | |
| 382 | assert box != null; | |
| 383 | ||
| 384 | int x = box.getAbsX(); | |
| 385 | ||
| 386 | // Scroll back up by half the height of the scroll bar to keep the typing | |
| 387 | // area within the view port. Otherwise the view port will have jumped too | |
| 388 | // high up and the whatever gets typed won't be visible. | |
| 389 | int y = max( | |
| 390 | box.getAbsY() - (mScrollPane.getVerticalScrollBar().getHeight() / 2), | |
| 391 | 0 ); | |
| 392 | ||
| 393 | if( !box.getStyle().isInline() ) { | |
| 394 | final var margin = box.getMargin( mHtmlRenderer.getLayoutContext() ); | |
| 395 | x += margin.left(); | |
| 396 | y += margin.top(); | |
| 397 | } | |
| 398 | ||
| 399 | return new Point( x, y ); | |
| 400 | } | |
| 401 | ||
| 402 | private String getBaseUrl() { | |
| 403 | final Path basePath = getPath(); | |
| 404 | final Path parent = basePath == null ? null : basePath.getParent(); | |
| 405 | ||
| 406 | return parent == null ? "" : parent.toUri().toString(); | |
| 407 | } | |
| 408 | ||
| 409 | private SharedContext getSharedContext() { | |
| 410 | return mHtmlRenderer.getSharedContext(); | |
| 411 | } | |
| 412 | } | |
| 1 | 413 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import com.whitemagicsoftware.tex.*; | |
| 31 | import com.whitemagicsoftware.tex.graphics.SvgDomGraphics2D; | |
| 32 | import org.w3c.dom.Document; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for rendering formulas as scalable vector graphics (SVG). | |
| 36 | */ | |
| 37 | public class MathRenderer { | |
| 38 | ||
| 39 | private static final float mSize = 20f; | |
| 40 | ||
| 41 | private final TeXFont mTeXFont = new DefaultTeXFont( mSize ); | |
| 42 | private final TeXEnvironment mEnvironment = new TeXEnvironment( mTeXFont ); | |
| 43 | private final SvgDomGraphics2D mGraphics = new SvgDomGraphics2D(); | |
| 44 | ||
| 45 | public MathRenderer() { | |
| 46 | mGraphics.scale( mSize, mSize ); | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * This method only takes a few seconds to generate | |
| 51 | * | |
| 52 | * @param equation A mathematical expression to render. | |
| 53 | * @return The given string with all formulas transformed into SVG format. | |
| 54 | */ | |
| 55 | public Document render( final String equation ) { | |
| 56 | final var formula = new TeXFormula( equation ); | |
| 57 | final var box = formula.createBox( mEnvironment ); | |
| 58 | final var l = new TeXLayout( box, mSize ); | |
| 59 | ||
| 60 | mGraphics.initialize( l.getWidth(), l.getHeight() ); | |
| 61 | box.draw( mGraphics, l.getX(), l.getY() ); | |
| 62 | return mGraphics.toDom(); | |
| 63 | } | |
| 64 | } | |
| 1 | 65 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import java.util.HashMap; | |
| 31 | import java.util.Map; | |
| 32 | ||
| 33 | import static java.awt.RenderingHints.*; | |
| 34 | import static java.awt.Toolkit.getDefaultToolkit; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for supplying consistent rendering hints throughout the | |
| 38 | * application, such as image rendering for {@link SvgRasterizer}. | |
| 39 | */ | |
| 40 | @SuppressWarnings("rawtypes") | |
| 41 | public class RenderingSettings { | |
| 42 | ||
| 43 | /** | |
| 44 | * Default hints for high-quality rendering that may be changed by | |
| 45 | * the system's rendering hints. | |
| 46 | */ | |
| 47 | private static final Map<Object, Object> DEFAULT_HINTS = Map.of( | |
| 48 | KEY_ANTIALIASING, | |
| 49 | VALUE_ANTIALIAS_ON, | |
| 50 | KEY_ALPHA_INTERPOLATION, | |
| 51 | VALUE_ALPHA_INTERPOLATION_QUALITY, | |
| 52 | KEY_COLOR_RENDERING, | |
| 53 | VALUE_COLOR_RENDER_QUALITY, | |
| 54 | KEY_DITHERING, | |
| 55 | VALUE_DITHER_DISABLE, | |
| 56 | KEY_FRACTIONALMETRICS, | |
| 57 | VALUE_FRACTIONALMETRICS_ON, | |
| 58 | KEY_INTERPOLATION, | |
| 59 | VALUE_INTERPOLATION_BICUBIC, | |
| 60 | KEY_RENDERING, | |
| 61 | VALUE_RENDER_QUALITY, | |
| 62 | KEY_STROKE_CONTROL, | |
| 63 | VALUE_STROKE_PURE, | |
| 64 | KEY_TEXT_ANTIALIASING, | |
| 65 | VALUE_TEXT_ANTIALIAS_ON | |
| 66 | ); | |
| 67 | ||
| 68 | /** | |
| 69 | * Shared hints for high-quality rendering. | |
| 70 | */ | |
| 71 | public static final Map<Object, Object> RENDERING_HINTS = new HashMap<>( | |
| 72 | DEFAULT_HINTS | |
| 73 | ); | |
| 74 | ||
| 75 | static { | |
| 76 | final var toolkit = getDefaultToolkit(); | |
| 77 | final var hints = toolkit.getDesktopProperty( "awt.font.desktophints" ); | |
| 78 | ||
| 79 | if( hints instanceof Map ) { | |
| 80 | final var map = (Map) hints; | |
| 81 | for( final var key : map.keySet() ) { | |
| 82 | final var hint = map.get( key ); | |
| 83 | RENDERING_HINTS.put( key, hint ); | |
| 84 | } | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Prevent instantiation as per Joshua Bloch's recommendation. | |
| 90 | */ | |
| 91 | private RenderingSettings() { | |
| 92 | } | |
| 93 | } | |
| 1 | 94 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import org.apache.batik.anim.dom.SAXSVGDocumentFactory; | |
| 31 | import org.apache.batik.gvt.renderer.ImageRenderer; | |
| 32 | import org.apache.batik.transcoder.TranscoderException; | |
| 33 | import org.apache.batik.transcoder.TranscoderInput; | |
| 34 | import org.apache.batik.transcoder.TranscoderOutput; | |
| 35 | import org.apache.batik.transcoder.image.ImageTranscoder; | |
| 36 | import org.w3c.dom.Document; | |
| 37 | import org.w3c.dom.Element; | |
| 38 | ||
| 39 | import javax.xml.transform.Transformer; | |
| 40 | import javax.xml.transform.TransformerConfigurationException; | |
| 41 | import javax.xml.transform.TransformerFactory; | |
| 42 | import javax.xml.transform.dom.DOMSource; | |
| 43 | import javax.xml.transform.stream.StreamResult; | |
| 44 | import java.awt.*; | |
| 45 | import java.awt.image.BufferedImage; | |
| 46 | import java.io.IOException; | |
| 47 | import java.io.StringReader; | |
| 48 | import java.io.StringWriter; | |
| 49 | import java.net.URL; | |
| 50 | import java.text.NumberFormat; | |
| 51 | ||
| 52 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 53 | import static com.scrivenvar.preview.RenderingSettings.RENDERING_HINTS; | |
| 54 | import static java.awt.image.BufferedImage.TYPE_INT_RGB; | |
| 55 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 56 | import static java.text.NumberFormat.getIntegerInstance; | |
| 57 | import static javax.xml.transform.OutputKeys.*; | |
| 58 | import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH; | |
| 59 | import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName; | |
| 60 | ||
| 61 | /** | |
| 62 | * Responsible for converting SVG images into rasterized PNG images. | |
| 63 | */ | |
| 64 | public class SvgRasterizer { | |
| 65 | private static final SAXSVGDocumentFactory FACTORY_DOM = | |
| 66 | new SAXSVGDocumentFactory( getXMLParserClassName() ); | |
| 67 | ||
| 68 | private static final TransformerFactory FACTORY_TRANSFORM = | |
| 69 | TransformerFactory.newInstance(); | |
| 70 | ||
| 71 | private static final Transformer sTransformer; | |
| 72 | ||
| 73 | static { | |
| 74 | Transformer t; | |
| 75 | ||
| 76 | try { | |
| 77 | t = FACTORY_TRANSFORM.newTransformer(); | |
| 78 | t.setOutputProperty( OMIT_XML_DECLARATION, "yes" ); | |
| 79 | t.setOutputProperty( METHOD, "xml" ); | |
| 80 | t.setOutputProperty( INDENT, "no" ); | |
| 81 | t.setOutputProperty( ENCODING, UTF_8.name() ); | |
| 82 | } catch( final TransformerConfigurationException e ) { | |
| 83 | t = null; | |
| 84 | } | |
| 85 | ||
| 86 | sTransformer = t; | |
| 87 | } | |
| 88 | ||
| 89 | private static final NumberFormat INT_FORMAT = getIntegerInstance(); | |
| 90 | ||
| 91 | public static final BufferedImage BROKEN_IMAGE_PLACEHOLDER; | |
| 92 | ||
| 93 | /** | |
| 94 | * A FontAwesome camera icon, cleft asunder. | |
| 95 | */ | |
| 96 | public static final String BROKEN_IMAGE_SVG = | |
| 97 | "<svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www" + | |
| 98 | ".w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c" + | |
| 99 | ".332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path " + | |
| 100 | "d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531" + | |
| 101 | ".03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2" + | |
| 102 | ".511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-" + | |
| 103 | ".367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1" + | |
| 104 | ".699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 " + | |
| 105 | "2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094" + | |
| 106 | ".0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5" + | |
| 107 | ".0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-" + | |
| 108 | ".195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4" + | |
| 109 | ".191406-3.652344.207031-.015625.414063-.015625.617187-.007812l" + | |
| 110 | ".933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2" + | |
| 111 | ".820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3" + | |
| 112 | ".429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3" + | |
| 113 | ".429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 " + | |
| 114 | ".140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281" + | |
| 115 | ".808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2" + | |
| 116 | ".472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4" + | |
| 117 | ".285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1" + | |
| 118 | ".9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-" + | |
| 119 | ".667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1" + | |
| 120 | ".253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 " + | |
| 121 | "0'/></g></svg>"; | |
| 122 | ||
| 123 | static { | |
| 124 | // The width and height cannot be embedded in the SVG above because the | |
| 125 | // path element values are relative to the viewBox dimensions. | |
| 126 | final int w = 75; | |
| 127 | final int h = 75; | |
| 128 | BufferedImage image; | |
| 129 | ||
| 130 | try { | |
| 131 | image = rasterizeString( BROKEN_IMAGE_SVG, w ); | |
| 132 | } catch( final Exception e ) { | |
| 133 | image = new BufferedImage( w, h, TYPE_INT_RGB ); | |
| 134 | final var graphics = (Graphics2D) image.getGraphics(); | |
| 135 | graphics.setRenderingHints( RENDERING_HINTS ); | |
| 136 | ||
| 137 | // Fall back to a (\) symbol. | |
| 138 | graphics.setColor( new Color( 204, 204, 204 ) ); | |
| 139 | graphics.fillRect( 0, 0, w, h ); | |
| 140 | graphics.setColor( new Color( 255, 204, 204 ) ); | |
| 141 | graphics.setStroke( new BasicStroke( 4 ) ); | |
| 142 | graphics.drawOval( w / 4, h / 4, w / 2, h / 2 ); | |
| 143 | graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI), | |
| 144 | h / 4 + (int) (w / 4 / Math.PI), | |
| 145 | w / 2 + w / 4 - (int) (w / 4 / Math.PI), | |
| 146 | h / 2 + h / 4 - (int) (w / 4 / Math.PI) ); | |
| 147 | } | |
| 148 | ||
| 149 | BROKEN_IMAGE_PLACEHOLDER = image; | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Responsible for creating a new {@link ImageRenderer} implementation that | |
| 154 | * can render a DOM as an SVG image. | |
| 155 | */ | |
| 156 | private static class BufferedImageTranscoder extends ImageTranscoder { | |
| 157 | private BufferedImage mImage; | |
| 158 | ||
| 159 | @Override | |
| 160 | public BufferedImage createImage( final int w, final int h ) { | |
| 161 | return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB ); | |
| 162 | } | |
| 163 | ||
| 164 | @Override | |
| 165 | public void writeImage( | |
| 166 | final BufferedImage image, final TranscoderOutput output ) { | |
| 167 | mImage = image; | |
| 168 | } | |
| 169 | ||
| 170 | public BufferedImage getImage() { | |
| 171 | return mImage; | |
| 172 | } | |
| 173 | ||
| 174 | @Override | |
| 175 | protected ImageRenderer createRenderer() { | |
| 176 | final ImageRenderer renderer = super.createRenderer(); | |
| 177 | final RenderingHints hints = renderer.getRenderingHints(); | |
| 178 | hints.putAll( RENDERING_HINTS ); | |
| 179 | ||
| 180 | renderer.setRenderingHints( hints ); | |
| 181 | ||
| 182 | return renderer; | |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | /** | |
| 187 | * Rasterizes the vector graphic file at the given URL. If any exception | |
| 188 | * happens, a red circle is returned instead. | |
| 189 | * | |
| 190 | * @param url The URL to a vector graphic file, which must include the | |
| 191 | * protocol scheme (such as file:// or https://). | |
| 192 | * @param width The number of pixels wide to render the image. The aspect | |
| 193 | * ratio is maintained. | |
| 194 | * @return Either the rasterized image upon success or a red circle. | |
| 195 | */ | |
| 196 | public static BufferedImage rasterize( final String url, final int width ) { | |
| 197 | try { | |
| 198 | return rasterize( new URL( url ), width ); | |
| 199 | } catch( final Exception ex ) { | |
| 200 | alert( ex ); | |
| 201 | return BROKEN_IMAGE_PLACEHOLDER; | |
| 202 | } | |
| 203 | } | |
| 204 | ||
| 205 | /** | |
| 206 | * Rasterizes the given document into an image. | |
| 207 | * | |
| 208 | * @param svg The SVG {@link Document} to rasterize. | |
| 209 | * @param width The rasterized image's width (in pixels). | |
| 210 | * @return The rasterized image. | |
| 211 | * @throws TranscoderException Signifies an issue with the input document. | |
| 212 | */ | |
| 213 | public static BufferedImage rasterize( final Document svg, final int width ) | |
| 214 | throws TranscoderException { | |
| 215 | final var transcoder = new BufferedImageTranscoder(); | |
| 216 | final var input = new TranscoderInput( svg ); | |
| 217 | ||
| 218 | transcoder.addTranscodingHint( KEY_WIDTH, (float) width ); | |
| 219 | transcoder.transcode( input, null ); | |
| 220 | ||
| 221 | return transcoder.getImage(); | |
| 222 | } | |
| 223 | ||
| 224 | /** | |
| 225 | * Converts an SVG drawing into a rasterized image that can be drawn on | |
| 226 | * a graphics context. | |
| 227 | * | |
| 228 | * @param url The path to the image (can be web address). | |
| 229 | * @param width Scale the image width to this size (aspect ratio is | |
| 230 | * maintained). | |
| 231 | * @return The vector graphic transcoded into a raster image format. | |
| 232 | * @throws IOException Could not read the vector graphic. | |
| 233 | * @throws TranscoderException Could not convert the vector graphic to an | |
| 234 | * instance of {@link Image}. | |
| 235 | */ | |
| 236 | public static BufferedImage rasterize( final URL url, final int width ) | |
| 237 | throws IOException, TranscoderException { | |
| 238 | return rasterize( FACTORY_DOM.createDocument( url.toString() ), width ); | |
| 239 | } | |
| 240 | ||
| 241 | public static BufferedImage rasterize( final Document document ) { | |
| 242 | try { | |
| 243 | final var root = document.getDocumentElement(); | |
| 244 | final var width = root.getAttribute( "width" ); | |
| 245 | return rasterize( document, INT_FORMAT.parse( width ).intValue() ); | |
| 246 | } catch( final Exception ex ) { | |
| 247 | alert( ex ); | |
| 248 | return BROKEN_IMAGE_PLACEHOLDER; | |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | /** | |
| 253 | * Converts an SVG string into a rasterized image that can be drawn on | |
| 254 | * a graphics context. | |
| 255 | * | |
| 256 | * @param svg The SVG xml document. | |
| 257 | * @param w Scale the image width to this size (aspect ratio is | |
| 258 | * maintained). | |
| 259 | * @return The vector graphic transcoded into a raster image format. | |
| 260 | * @throws TranscoderException Could not convert the vector graphic to an | |
| 261 | * instance of {@link Image}. | |
| 262 | */ | |
| 263 | public static BufferedImage rasterizeString( final String svg, final int w ) | |
| 264 | throws IOException, TranscoderException { | |
| 265 | return rasterize( toDocument( svg ), w ); | |
| 266 | } | |
| 267 | ||
| 268 | /** | |
| 269 | * Converts an SVG string into a rasterized image that can be drawn on | |
| 270 | * a graphics context. The dimensions are determined from the document. | |
| 271 | * | |
| 272 | * @param xml The SVG xml document. | |
| 273 | * @return The vector graphic transcoded into a raster image format. | |
| 274 | */ | |
| 275 | public static BufferedImage rasterizeString( final String xml ) { | |
| 276 | try { | |
| 277 | final var document = toDocument( xml ); | |
| 278 | final var root = document.getDocumentElement(); | |
| 279 | final var width = root.getAttribute( "width" ); | |
| 280 | return rasterizeString( xml, INT_FORMAT.parse( width ).intValue() ); | |
| 281 | } catch( final Exception ex ) { | |
| 282 | alert( ex ); | |
| 283 | return BROKEN_IMAGE_PLACEHOLDER; | |
| 284 | } | |
| 285 | } | |
| 286 | ||
| 287 | /** | |
| 288 | * Converts an SVG XML string into a new {@link Document} instance. | |
| 289 | * | |
| 290 | * @param xml The XML containing SVG elements. | |
| 291 | * @return The SVG contents parsed into a {@link Document} object model. | |
| 292 | * @throws IOException Could | |
| 293 | */ | |
| 294 | private static Document toDocument( final String xml ) throws IOException { | |
| 295 | try( final var reader = new StringReader( xml ) ) { | |
| 296 | return FACTORY_DOM.createSVGDocument( | |
| 297 | "http://www.w3.org/2000/svg", reader ); | |
| 298 | } | |
| 299 | } | |
| 300 | ||
| 301 | /** | |
| 302 | * Given a document object model (DOM) {@link Element}, this will convert that | |
| 303 | * element to a string. | |
| 304 | * | |
| 305 | * @param e The DOM node to convert to a string. | |
| 306 | * @return The DOM node as an escaped, plain text string. | |
| 307 | */ | |
| 308 | public static String toSvg( final Element e ) { | |
| 309 | try( final var writer = new StringWriter() ) { | |
| 310 | sTransformer.transform( new DOMSource( e ), new StreamResult( writer ) ); | |
| 311 | return writer.toString().replaceAll( "xmlns=\"\" ", "" ); | |
| 312 | } catch( final Exception ex ) { | |
| 313 | alert( ex ); | |
| 314 | } | |
| 315 | ||
| 316 | return BROKEN_IMAGE_SVG; | |
| 317 | } | |
| 318 | } | |
| 1 | 319 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.preview; | |
| 29 | ||
| 30 | import com.scrivenvar.util.BoundedCache; | |
| 31 | import org.apache.commons.io.FilenameUtils; | |
| 32 | import org.w3c.dom.Element; | |
| 33 | import org.xhtmlrenderer.extend.ReplacedElement; | |
| 34 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 35 | import org.xhtmlrenderer.extend.UserAgentCallback; | |
| 36 | import org.xhtmlrenderer.layout.LayoutContext; | |
| 37 | import org.xhtmlrenderer.render.BlockBox; | |
| 38 | import org.xhtmlrenderer.simple.extend.FormSubmissionListener; | |
| 39 | import org.xhtmlrenderer.swing.ImageReplacedElement; | |
| 40 | ||
| 41 | import java.awt.image.BufferedImage; | |
| 42 | import java.util.Map; | |
| 43 | import java.util.function.Function; | |
| 44 | ||
| 45 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 46 | import static com.scrivenvar.preview.SvgRasterizer.rasterize; | |
| 47 | ||
| 48 | /** | |
| 49 | * Responsible for running {@link SvgRasterizer} on SVG images detected within | |
| 50 | * a document to transform them into rasterized versions. | |
| 51 | */ | |
| 52 | public class SvgReplacedElementFactory | |
| 53 | implements ReplacedElementFactory { | |
| 54 | ||
| 55 | /** | |
| 56 | * SVG filename extension maps to an SVG image element. | |
| 57 | */ | |
| 58 | private static final String SVG_FILE = "svg"; | |
| 59 | ||
| 60 | /** | |
| 61 | * TeX expression wrapped in a {@code <tex>} element. | |
| 62 | */ | |
| 63 | private static final String HTML_TEX = "tex"; | |
| 64 | ||
| 65 | private static final String HTML_IMAGE = "img"; | |
| 66 | private static final String HTML_IMAGE_SRC = "src"; | |
| 67 | ||
| 68 | private static final MathRenderer sMathRenderer = new MathRenderer(); | |
| 69 | ||
| 70 | /** | |
| 71 | * A bounded cache that removes the oldest image if the maximum number of | |
| 72 | * cached images has been reached. This constrains the number of images | |
| 73 | * loaded into memory. | |
| 74 | */ | |
| 75 | private final Map<String, BufferedImage> mImageCache = | |
| 76 | new BoundedCache<>( 150 ); | |
| 77 | ||
| 78 | @Override | |
| 79 | public ReplacedElement createReplacedElement( | |
| 80 | final LayoutContext c, | |
| 81 | final BlockBox box, | |
| 82 | final UserAgentCallback uac, | |
| 83 | final int cssWidth, | |
| 84 | final int cssHeight ) { | |
| 85 | BufferedImage image = null; | |
| 86 | final var e = box.getElement(); | |
| 87 | ||
| 88 | if( e != null ) { | |
| 89 | try { | |
| 90 | final var nodeName = e.getNodeName(); | |
| 91 | ||
| 92 | if( HTML_IMAGE.equals( nodeName ) ) { | |
| 93 | final var src = e.getAttribute( HTML_IMAGE_SRC ); | |
| 94 | final var ext = FilenameUtils.getExtension( src ); | |
| 95 | ||
| 96 | if( SVG_FILE.equalsIgnoreCase( ext ) ) { | |
| 97 | image = getCachedImage( | |
| 98 | src, svg -> rasterize( svg, box.getContentWidth() ) ); | |
| 99 | } | |
| 100 | } | |
| 101 | else if( HTML_TEX.equals( nodeName ) ) { | |
| 102 | // Convert the <svg> element to a raster graphic if not yet cached. | |
| 103 | final var src = e.getTextContent(); | |
| 104 | image = getCachedImage( | |
| 105 | src, __ -> rasterize( sMathRenderer.render( src ) ) | |
| 106 | ); | |
| 107 | } | |
| 108 | } catch( final Exception ex ) { | |
| 109 | alert( ex ); | |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | if( image != null ) { | |
| 114 | final var w = image.getWidth( null ); | |
| 115 | final var h = image.getHeight( null ); | |
| 116 | ||
| 117 | return new ImageReplacedElement( image, w, h ); | |
| 118 | } | |
| 119 | ||
| 120 | return null; | |
| 121 | } | |
| 122 | ||
| 123 | @Override | |
| 124 | public void reset() { | |
| 125 | } | |
| 126 | ||
| 127 | @Override | |
| 128 | public void remove( final Element e ) { | |
| 129 | } | |
| 130 | ||
| 131 | @Override | |
| 132 | public void setFormSubmissionListener( FormSubmissionListener listener ) { | |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * Returns an image associated with a string; the string's pre-computed | |
| 137 | * hash code is returned as the string value, making this operation very | |
| 138 | * quick to return the corresponding {@link BufferedImage}. | |
| 139 | * | |
| 140 | * @param src The SVG used for the key into the image cache. | |
| 141 | * @param rasterizer {@link Function} to call to convert SVG to an image. | |
| 142 | * @return The image that corresponds to the given source string. | |
| 143 | */ | |
| 144 | private BufferedImage getCachedImage( | |
| 145 | final String src, final Function<String, BufferedImage> rasterizer ) { | |
| 146 | return mImageCache.computeIfAbsent( src, __ -> rasterizer.apply( src ) ); | |
| 147 | } | |
| 148 | } | |
| 1 | 149 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for transforming a document through a variety of chained | |
| 32 | * handlers. If there are conditions where this handler should not process the | |
| 33 | * entire chain, create a second handler, or split the chain into reusable | |
| 34 | * sub-chains. | |
| 35 | * | |
| 36 | * @param <T> The type of object to process. | |
| 37 | */ | |
| 38 | public abstract class AbstractProcessor<T> implements Processor<T> { | |
| 39 | ||
| 40 | /** | |
| 41 | * Used while processing the entire chain; null to signify no more links. | |
| 42 | */ | |
| 43 | private final Processor<T> mNext; | |
| 44 | ||
| 45 | /** | |
| 46 | * Constructs a new default handler with no successor. | |
| 47 | */ | |
| 48 | protected AbstractProcessor() { | |
| 49 | this( null ); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Constructs a new default handler with a given successor. | |
| 54 | * | |
| 55 | * @param successor The next processor in the chain. | |
| 56 | */ | |
| 57 | public AbstractProcessor( final Processor<T> successor ) { | |
| 58 | mNext = successor; | |
| 59 | } | |
| 60 | ||
| 61 | @Override | |
| 62 | public Processor<T> next() { | |
| 63 | return mNext; | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * This algorithm is incorrect, but works for the one use case of removing | |
| 68 | * the ending HTML Preview Processor from the end of the processor chain. | |
| 69 | * The processor chain is immutable so this creates a succession of | |
| 70 | * delegators that wrap each processor in the chain, except for the one | |
| 71 | * to be removed. | |
| 72 | * <p> | |
| 73 | * An alternative is to update the {@link ProcessorFactory} with the ability | |
| 74 | * to create a processor chain devoid of an {@link HtmlPreviewProcessor}. | |
| 75 | * </p> | |
| 76 | * | |
| 77 | * @param removal The {@link Processor} to remove from the chain. | |
| 78 | * @return A delegating processor chain starting from this processor | |
| 79 | * onwards with the given processor removed from the chain. | |
| 80 | */ | |
| 81 | @Override | |
| 82 | public Processor<T> remove( final Class<? extends Processor<T>> removal ) { | |
| 83 | Processor<T> p = this; | |
| 84 | final ProcessorDelegator<T> head = new ProcessorDelegator<>( p ); | |
| 85 | ProcessorDelegator<T> result = head; | |
| 86 | ||
| 87 | while( p != null ) { | |
| 88 | final Processor<T> next = p.next(); | |
| 89 | ||
| 90 | if( next != null && next.getClass() != removal ) { | |
| 91 | final var delegator = new ProcessorDelegator<>( next ); | |
| 92 | ||
| 93 | result.setNext( delegator ); | |
| 94 | result = delegator; | |
| 95 | } | |
| 96 | ||
| 97 | p = p.next(); | |
| 98 | } | |
| 99 | ||
| 100 | return head; | |
| 101 | } | |
| 102 | ||
| 103 | private static final class ProcessorDelegator<T> | |
| 104 | extends AbstractProcessor<T> { | |
| 105 | private final Processor<T> mDelegate; | |
| 106 | private Processor<T> mNext; | |
| 107 | ||
| 108 | public ProcessorDelegator( final Processor<T> delegate ) { | |
| 109 | super( delegate ); | |
| 110 | ||
| 111 | assert delegate != null; | |
| 112 | ||
| 113 | mDelegate = delegate; | |
| 114 | } | |
| 115 | ||
| 116 | @Override | |
| 117 | public T apply( T t ) { | |
| 118 | return mDelegate.apply( t ); | |
| 119 | } | |
| 120 | ||
| 121 | protected void setNext( final Processor<T> next ) { | |
| 122 | mNext = next; | |
| 123 | } | |
| 124 | ||
| 125 | @Override | |
| 126 | public Processor<T> next() { | |
| 127 | return mNext; | |
| 128 | } | |
| 129 | } | |
| 130 | } | |
| 1 | 131 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | import static com.scrivenvar.processors.text.TextReplacementFactory.replace; | |
| 33 | ||
| 34 | /** | |
| 35 | * Processes interpolated string definitions in the document and inserts | |
| 36 | * their values into the post-processed text. The default variable syntax is | |
| 37 | * {@code $variable$}. | |
| 38 | */ | |
| 39 | public class DefinitionProcessor extends AbstractProcessor<String> { | |
| 40 | ||
| 41 | private final Map<String, String> mDefinitions; | |
| 42 | ||
| 43 | public DefinitionProcessor( | |
| 44 | final Processor<String> successor, final Map<String, String> map ) { | |
| 45 | super( successor ); | |
| 46 | mDefinitions = map; | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * Processes the given text document by replacing variables with their values. | |
| 51 | * | |
| 52 | * @param text The document text that includes variables that should be | |
| 53 | * replaced with values when rendered as HTML. | |
| 54 | * @return The text with all variables replaced. | |
| 55 | */ | |
| 56 | @Override | |
| 57 | public String apply( final String text ) { | |
| 58 | return replace( text, getDefinitions() ); | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Returns the map to use for variable substitution. | |
| 63 | * | |
| 64 | * @return A map of variable names to values. | |
| 65 | */ | |
| 66 | protected Map<String, String> getDefinitions() { | |
| 67 | return mDefinitions; | |
| 68 | } | |
| 69 | } | |
| 1 | 70 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for notifying the HTMLPreviewPane when the succession chain has | |
| 34 | * updated. This decouples knowledge of changes to the editor panel from the | |
| 35 | * HTML preview panel as well as any processing that takes place before the | |
| 36 | * final HTML preview is rendered. This should be the last link in the processor | |
| 37 | * chain. | |
| 38 | */ | |
| 39 | public class HtmlPreviewProcessor extends AbstractProcessor<String> { | |
| 40 | ||
| 41 | // There is only one preview panel. | |
| 42 | private static HTMLPreviewPane sHtmlPreviewPane; | |
| 43 | ||
| 44 | /** | |
| 45 | * Constructs the end of a processing chain. | |
| 46 | * | |
| 47 | * @param htmlPreviewPane The pane to update with the post-processed document. | |
| 48 | */ | |
| 49 | public HtmlPreviewProcessor( final HTMLPreviewPane htmlPreviewPane ) { | |
| 50 | sHtmlPreviewPane = htmlPreviewPane; | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Update the preview panel using HTML from the succession chain. | |
| 55 | * | |
| 56 | * @param html The document content to render in the preview pane. The HTML | |
| 57 | * should not contain a doctype, head, or body tag, only | |
| 58 | * content to render within the body. | |
| 59 | * @return {@code null} to indicate no more processors in the chain. | |
| 60 | */ | |
| 61 | @Override | |
| 62 | public String apply( final String html ) { | |
| 63 | getHtmlPreviewPane().process( html ); | |
| 64 | ||
| 65 | // No more processing required. | |
| 66 | return null; | |
| 67 | } | |
| 68 | ||
| 69 | private HTMLPreviewPane getHtmlPreviewPane() { | |
| 70 | return sHtmlPreviewPane; | |
| 71 | } | |
| 72 | } | |
| 1 | 73 |
| 1 | /* | |
| 2 | * Copyright 2017 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | /** | |
| 31 | * This is the default processor used when an unknown filename extension is | |
| 32 | * encountered. | |
| 33 | */ | |
| 34 | public class IdentityProcessor extends AbstractProcessor<String> { | |
| 35 | ||
| 36 | /** | |
| 37 | * Passes the link to the super constructor. | |
| 38 | * | |
| 39 | * @param successor The next processor in the chain to use for text | |
| 40 | * processing. | |
| 41 | */ | |
| 42 | public IdentityProcessor( final Processor<String> successor ) { | |
| 43 | super( successor ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Returns the given string, modified with "pre" tags. | |
| 48 | * | |
| 49 | * @param t The string to return, enclosed in "pre" tags. | |
| 50 | * @return The value of t wrapped in "pre" tags. | |
| 51 | */ | |
| 52 | @Override | |
| 53 | public String apply( final String t ) { | |
| 54 | return "<pre>" + t + "</pre>"; | |
| 55 | } | |
| 56 | } | |
| 1 | 57 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.preferences.UserPreferences; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | import javafx.beans.property.ObjectProperty; | |
| 34 | import javafx.beans.property.StringProperty; | |
| 35 | ||
| 36 | import javax.script.ScriptEngine; | |
| 37 | import javax.script.ScriptEngineManager; | |
| 38 | import java.io.File; | |
| 39 | import java.nio.file.Path; | |
| 40 | import java.util.LinkedHashMap; | |
| 41 | import java.util.Map; | |
| 42 | import java.util.concurrent.atomic.AtomicBoolean; | |
| 43 | ||
| 44 | import static com.scrivenvar.Constants.STATUS_PARSE_ERROR; | |
| 45 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 46 | import static com.scrivenvar.processors.text.TextReplacementFactory.replace; | |
| 47 | import static com.scrivenvar.sigils.RSigilOperator.PREFIX; | |
| 48 | import static com.scrivenvar.sigils.RSigilOperator.SUFFIX; | |
| 49 | import static java.lang.Math.min; | |
| 50 | ||
| 51 | /** | |
| 52 | * Transforms a document containing R statements into Markdown. | |
| 53 | */ | |
| 54 | public final class InlineRProcessor extends DefinitionProcessor { | |
| 55 | ||
| 56 | private static final Options sOptions = Services.load( Options.class ); | |
| 57 | ||
| 58 | /** | |
| 59 | * Constrain memory when typing new R expressions into the document. | |
| 60 | */ | |
| 61 | private static final int MAX_CACHED_R_STATEMENTS = 512; | |
| 62 | ||
| 63 | /** | |
| 64 | * Where to put document inline evaluated R expressions. | |
| 65 | */ | |
| 66 | private final Map<String, Object> mEvalCache = new LinkedHashMap<>() { | |
| 67 | @Override | |
| 68 | protected boolean removeEldestEntry( | |
| 69 | final Map.Entry<String, Object> eldest ) { | |
| 70 | return size() > MAX_CACHED_R_STATEMENTS; | |
| 71 | } | |
| 72 | }; | |
| 73 | ||
| 74 | /** | |
| 75 | * Only one editor is open at a time. | |
| 76 | */ | |
| 77 | private static final ScriptEngine ENGINE = | |
| 78 | (new ScriptEngineManager()).getEngineByName( "Renjin" ); | |
| 79 | ||
| 80 | private static final int PREFIX_LENGTH = PREFIX.length(); | |
| 81 | ||
| 82 | private final AtomicBoolean mDirty = new AtomicBoolean( false ); | |
| 83 | ||
| 84 | /** | |
| 85 | * Constructs a processor capable of evaluating R statements. | |
| 86 | * | |
| 87 | * @param successor Subsequent link in the processing chain. | |
| 88 | * @param map Resolved definitions map. | |
| 89 | */ | |
| 90 | public InlineRProcessor( | |
| 91 | final Processor<String> successor, | |
| 92 | final Map<String, String> map ) { | |
| 93 | super( successor, map ); | |
| 94 | ||
| 95 | bootstrapScriptProperty().addListener( | |
| 96 | ( ob, oldScript, newScript ) -> setDirty( true ) ); | |
| 97 | workingDirectoryProperty().addListener( | |
| 98 | ( ob, oldScript, newScript ) -> setDirty( true ) ); | |
| 99 | ||
| 100 | getUserPreferences().addSaveEventHandler( ( handler ) -> { | |
| 101 | if( isDirty() ) { | |
| 102 | init(); | |
| 103 | setDirty( false ); | |
| 104 | } | |
| 105 | } ); | |
| 106 | ||
| 107 | init(); | |
| 108 | } | |
| 109 | ||
| 110 | /** | |
| 111 | * Initialises the R code so that R can find imported libraries. Note that | |
| 112 | * any existing R functionality will not be overwritten if this method is | |
| 113 | * called multiple times. | |
| 114 | */ | |
| 115 | private void init() { | |
| 116 | final var bootstrap = getBootstrapScript(); | |
| 117 | ||
| 118 | if( !bootstrap.isBlank() ) { | |
| 119 | final var wd = getWorkingDirectory(); | |
| 120 | final var dir = wd.toString().replace( '\\', '/' ); | |
| 121 | final var map = getDefinitions(); | |
| 122 | map.put( "$application.r.working.directory$", dir ); | |
| 123 | ||
| 124 | eval( replace( bootstrap, map ) ); | |
| 125 | } | |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Sets the dirty flag to indicate that the bootstrap script or working | |
| 130 | * directory has been modified. Upon saving the preferences, if this flag | |
| 131 | * is true, then {@link #init()} will be called to reload the R environment. | |
| 132 | * | |
| 133 | * @param dirty Set to true to reload changes upon closing preferences. | |
| 134 | */ | |
| 135 | private void setDirty( final boolean dirty ) { | |
| 136 | mDirty.set( dirty ); | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| 140 | * Answers whether R-related settings have been modified. | |
| 141 | * | |
| 142 | * @return {@code true} when the settings have changed. | |
| 143 | */ | |
| 144 | private boolean isDirty() { | |
| 145 | return mDirty.get(); | |
| 146 | } | |
| 147 | ||
| 148 | /** | |
| 149 | * Evaluates all R statements in the source document and inserts the | |
| 150 | * calculated value into the generated document. | |
| 151 | * | |
| 152 | * @param text The document text that includes variables that should be | |
| 153 | * replaced with values when rendered as HTML. | |
| 154 | * @return The generated document with output from all R statements | |
| 155 | * substituted with value returned from their execution. | |
| 156 | */ | |
| 157 | @Override | |
| 158 | public String apply( final String text ) { | |
| 159 | final int length = text.length(); | |
| 160 | ||
| 161 | // The * 2 is a wild guess at the ratio of R statements to the length | |
| 162 | // of text produced by those statements. | |
| 163 | final StringBuilder sb = new StringBuilder( length * 2 ); | |
| 164 | ||
| 165 | int prevIndex = 0; | |
| 166 | int currIndex = text.indexOf( PREFIX ); | |
| 167 | ||
| 168 | while( currIndex >= 0 ) { | |
| 169 | // Copy everything up to, but not including, an R statement (`r#). | |
| 170 | sb.append( text, prevIndex, currIndex ); | |
| 171 | ||
| 172 | // Jump to the start of the R statement. | |
| 173 | prevIndex = currIndex + PREFIX_LENGTH; | |
| 174 | ||
| 175 | // Find the statement ending (`), without indexing past the text boundary. | |
| 176 | currIndex = text.indexOf( SUFFIX, min( currIndex + 1, length ) ); | |
| 177 | ||
| 178 | // Only evaluate inline R statements that have end delimiters. | |
| 179 | if( currIndex > 1 ) { | |
| 180 | // Extract the inline R statement to be evaluated. | |
| 181 | final String r = text.substring( prevIndex, currIndex ); | |
| 182 | ||
| 183 | // Pass the R statement into the R engine for evaluation. | |
| 184 | try { | |
| 185 | final Object result = evalText( r ); | |
| 186 | ||
| 187 | // Append the string representation of the result into the text. | |
| 188 | sb.append( result ); | |
| 189 | } catch( final Exception e ) { | |
| 190 | // If the string couldn't be parsed using R, append the statement | |
| 191 | // that failed to parse, instead of its evaluated value. | |
| 192 | sb.append( PREFIX ).append( r ).append( SUFFIX ); | |
| 193 | ||
| 194 | // Tell the user that there was a problem. | |
| 195 | alert( STATUS_PARSE_ERROR, e.getMessage(), currIndex ); | |
| 196 | } | |
| 197 | ||
| 198 | // Retain the R statement's ending position in the text. | |
| 199 | prevIndex = currIndex + 1; | |
| 200 | } | |
| 201 | ||
| 202 | // Find the start of the next inline R statement. | |
| 203 | currIndex = text.indexOf( PREFIX, min( currIndex + 1, length ) ); | |
| 204 | } | |
| 205 | ||
| 206 | // Copy from the previous index to the end of the string. | |
| 207 | return sb.append( text.substring( min( prevIndex, length ) ) ).toString(); | |
| 208 | } | |
| 209 | ||
| 210 | /** | |
| 211 | * Look up an R expression from the cache then return the resulting object. | |
| 212 | * If the R expression hasn't been cached, it'll first be evaluated. | |
| 213 | * | |
| 214 | * @param r The expression to evaluate. | |
| 215 | * @return The object resulting from the evaluation. | |
| 216 | */ | |
| 217 | private Object evalText( final String r ) { | |
| 218 | return mEvalCache.computeIfAbsent( r, v -> eval( r ) ); | |
| 219 | } | |
| 220 | ||
| 221 | /** | |
| 222 | * Evaluate an R expression and return the resulting object. | |
| 223 | * | |
| 224 | * @param r The expression to evaluate. | |
| 225 | * @return The object resulting from the evaluation. | |
| 226 | */ | |
| 227 | private Object eval( final String r ) { | |
| 228 | try { | |
| 229 | return getScriptEngine().eval( r ); | |
| 230 | } catch( final Exception ex ) { | |
| 231 | final String expr = r.substring( 0, min( r.length(), 30 ) ); | |
| 232 | alert( "Main.status.error.r", expr, ex.getMessage() ); | |
| 233 | } | |
| 234 | ||
| 235 | return ""; | |
| 236 | } | |
| 237 | ||
| 238 | /** | |
| 239 | * Return the given path if not {@code null}, otherwise return the path to | |
| 240 | * the user's directory. | |
| 241 | * | |
| 242 | * @return A non-null path. | |
| 243 | */ | |
| 244 | private Path getWorkingDirectory() { | |
| 245 | return getUserPreferences().getRDirectory().toPath(); | |
| 246 | } | |
| 247 | ||
| 248 | private ObjectProperty<File> workingDirectoryProperty() { | |
| 249 | return getUserPreferences().rDirectoryProperty(); | |
| 250 | } | |
| 251 | ||
| 252 | /** | |
| 253 | * Loads the R init script from the application's persisted preferences. | |
| 254 | * | |
| 255 | * @return A non-null string, possibly empty. | |
| 256 | */ | |
| 257 | private String getBootstrapScript() { | |
| 258 | return getUserPreferences().getRScript(); | |
| 259 | } | |
| 260 | ||
| 261 | private StringProperty bootstrapScriptProperty() { | |
| 262 | return getUserPreferences().rScriptProperty(); | |
| 263 | } | |
| 264 | ||
| 265 | private UserPreferences getUserPreferences() { | |
| 266 | return sOptions.getUserPreferences(); | |
| 267 | } | |
| 268 | ||
| 269 | private ScriptEngine getScriptEngine() { | |
| 270 | return ENGINE; | |
| 271 | } | |
| 272 | } | |
| 1 | 273 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import java.util.function.UnaryOperator; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for processing documents from one known format to another. | |
| 34 | * Processes the given content providing a transformation from one document | |
| 35 | * format into another. For example, this could convert from XML to text using | |
| 36 | * an XSLT processor, or from markdown to HTML. | |
| 37 | * | |
| 38 | * @param <T> The type of processor to create. | |
| 39 | */ | |
| 40 | public interface Processor<T> extends UnaryOperator<T> { | |
| 41 | ||
| 42 | /** | |
| 43 | * Removes the given processor from the chain, returning a new immutable | |
| 44 | * chain equivalent to this chain, but without the given processor. | |
| 45 | * | |
| 46 | * @param processor The {@link Processor} to remove from the chain. | |
| 47 | * @return A delegating processor chain starting from this processor | |
| 48 | * onwards with the given processor removed from the chain. | |
| 49 | */ | |
| 50 | Processor<T> remove( Class<? extends Processor<T>> processor ); | |
| 51 | ||
| 52 | /** | |
| 53 | * Adds a document processor to call after this processor finishes processing | |
| 54 | * the document given to the process method. | |
| 55 | * | |
| 56 | * @return The processor that should transform the document after this | |
| 57 | * instance has finished processing, or {@code null} if this is the last | |
| 58 | * processor in the chain. | |
| 59 | */ | |
| 60 | default Processor<T> next() { | |
| 61 | return null; | |
| 62 | } | |
| 63 | } | |
| 1 | 64 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.AbstractFileFactory; | |
| 31 | import com.scrivenvar.FileEditorTab; | |
| 32 | import com.scrivenvar.preview.HTMLPreviewPane; | |
| 33 | import com.scrivenvar.processors.markdown.MarkdownProcessor; | |
| 34 | ||
| 35 | import java.util.Map; | |
| 36 | ||
| 37 | /** | |
| 38 | * Responsible for creating processors capable of parsing, transforming, | |
| 39 | * interpolating, and rendering known file types. | |
| 40 | */ | |
| 41 | public class ProcessorFactory extends AbstractFileFactory { | |
| 42 | ||
| 43 | private final HTMLPreviewPane mPreviewPane; | |
| 44 | private final Map<String, String> mResolvedMap; | |
| 45 | private final Processor<String> mMarkdownProcessor; | |
| 46 | ||
| 47 | /** | |
| 48 | * Constructs a factory with the ability to create processors that can perform | |
| 49 | * text and caret processing to generate a final preview. | |
| 50 | * | |
| 51 | * @param previewPane Where the final output is rendered. | |
| 52 | * @param resolvedMap Flat map of definitions to replace before final render. | |
| 53 | */ | |
| 54 | public ProcessorFactory( | |
| 55 | final HTMLPreviewPane previewPane, | |
| 56 | final Map<String, String> resolvedMap ) { | |
| 57 | mPreviewPane = previewPane; | |
| 58 | mResolvedMap = resolvedMap; | |
| 59 | mMarkdownProcessor = createMarkdownProcessor(); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Creates a processor chain suitable for parsing and rendering the file | |
| 64 | * opened at the given tab. | |
| 65 | * | |
| 66 | * @param tab The tab containing a text editor, path, and caret position. | |
| 67 | * @return A processor that can render the given tab's text. | |
| 68 | */ | |
| 69 | public Processor<String> createProcessors( final FileEditorTab tab ) { | |
| 70 | return switch( lookup( tab.getPath() ) ) { | |
| 71 | case RMARKDOWN -> createRProcessor(); | |
| 72 | case SOURCE -> createMarkdownDefinitionProcessor(); | |
| 73 | case XML -> createXMLProcessor( tab ); | |
| 74 | case RXML -> createRXMLProcessor( tab ); | |
| 75 | default -> createIdentityProcessor(); | |
| 76 | }; | |
| 77 | } | |
| 78 | ||
| 79 | private Processor<String> createHTMLPreviewProcessor() { | |
| 80 | return new HtmlPreviewProcessor( getPreviewPane() ); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Creates and links the processors at the end of the processing chain. | |
| 85 | * | |
| 86 | * @return A markdown, caret replacement, and preview pane processor chain. | |
| 87 | */ | |
| 88 | private Processor<String> createMarkdownProcessor() { | |
| 89 | final var hpp = createHTMLPreviewProcessor(); | |
| 90 | return new MarkdownProcessor( hpp, getPreviewPane().getPath() ); | |
| 91 | } | |
| 92 | ||
| 93 | protected Processor<String> createIdentityProcessor() { | |
| 94 | final var hpp = createHTMLPreviewProcessor(); | |
| 95 | return new IdentityProcessor( hpp ); | |
| 96 | } | |
| 97 | ||
| 98 | protected Processor<String> createDefinitionProcessor( | |
| 99 | final Processor<String> p ) { | |
| 100 | return new DefinitionProcessor( p, getResolvedMap() ); | |
| 101 | } | |
| 102 | ||
| 103 | protected Processor<String> createMarkdownDefinitionProcessor() { | |
| 104 | final var tpc = getCommonProcessor(); | |
| 105 | return createDefinitionProcessor( tpc ); | |
| 106 | } | |
| 107 | ||
| 108 | protected Processor<String> createXMLProcessor( final FileEditorTab tab ) { | |
| 109 | final var tpc = getCommonProcessor(); | |
| 110 | final var xmlp = new XmlProcessor( tpc, tab.getPath() ); | |
| 111 | return createDefinitionProcessor( xmlp ); | |
| 112 | } | |
| 113 | ||
| 114 | protected Processor<String> createRProcessor() { | |
| 115 | final var tpc = getCommonProcessor(); | |
| 116 | final var rp = new InlineRProcessor( tpc, getResolvedMap() ); | |
| 117 | return new RVariableProcessor( rp, getResolvedMap() ); | |
| 118 | } | |
| 119 | ||
| 120 | protected Processor<String> createRXMLProcessor( final FileEditorTab tab ) { | |
| 121 | final var tpc = getCommonProcessor(); | |
| 122 | final var xmlp = new XmlProcessor( tpc, tab.getPath() ); | |
| 123 | final var rp = new InlineRProcessor( xmlp, getResolvedMap() ); | |
| 124 | return new RVariableProcessor( rp, getResolvedMap() ); | |
| 125 | } | |
| 126 | ||
| 127 | private HTMLPreviewPane getPreviewPane() { | |
| 128 | return mPreviewPane; | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * Returns the variable map of interpolated definitions. | |
| 133 | * | |
| 134 | * @return A map to help dereference variables. | |
| 135 | */ | |
| 136 | private Map<String, String> getResolvedMap() { | |
| 137 | return mResolvedMap; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Returns a processor common to all processors: markdown, caret position | |
| 142 | * token replacer, and an HTML preview renderer. | |
| 143 | * | |
| 144 | * @return Processors at the end of the processing chain. | |
| 145 | */ | |
| 146 | private Processor<String> getCommonProcessor() { | |
| 147 | return mMarkdownProcessor; | |
| 148 | } | |
| 149 | } | |
| 1 | 150 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.sigils.RSigilOperator; | |
| 31 | ||
| 32 | import java.util.HashMap; | |
| 33 | import java.util.Map; | |
| 34 | ||
| 35 | /** | |
| 36 | * Converts the keys of the resolved map from default form to R form, then | |
| 37 | * performs a substitution on the text. The default R variable syntax is | |
| 38 | * {@code v$tree$leaf}. | |
| 39 | */ | |
| 40 | public class RVariableProcessor extends DefinitionProcessor { | |
| 41 | ||
| 42 | public RVariableProcessor( | |
| 43 | final Processor<String> rp, final Map<String, String> map ) { | |
| 44 | super( rp, map ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the R-based version of the interpolated variable definitions. | |
| 49 | * | |
| 50 | * @return Variable names transmogrified from the default syntax to R syntax. | |
| 51 | */ | |
| 52 | @Override | |
| 53 | protected Map<String, String> getDefinitions() { | |
| 54 | return toR( super.getDefinitions() ); | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Converts the given map from regular variables to R variables. | |
| 59 | * | |
| 60 | * @param map Map of variable names to values. | |
| 61 | * @return Map of R variables. | |
| 62 | */ | |
| 63 | private Map<String, String> toR( final Map<String, String> map ) { | |
| 64 | final var rMap = new HashMap<String, String>( map.size() ); | |
| 65 | ||
| 66 | for( final var entry : map.entrySet() ) { | |
| 67 | final var key = entry.getKey(); | |
| 68 | rMap.put( RSigilOperator.entoken( key ), toRValue( map.get( key ) ) ); | |
| 69 | } | |
| 70 | ||
| 71 | return rMap; | |
| 72 | } | |
| 73 | ||
| 74 | private String toRValue( final String value ) { | |
| 75 | return '\'' + escape( value, '\'', "\\'" ) + '\''; | |
| 76 | } | |
| 77 | ||
| 78 | /** | |
| 79 | * TODO: Make generic method for replacing text. | |
| 80 | * | |
| 81 | * @param haystack Search this string for the needle, must not be null. | |
| 82 | * @param needle The character to find in the haystack. | |
| 83 | * @param thread Replace the needle with this text, if the needle is found. | |
| 84 | * @return The haystack with the all instances of needle replaced with thread. | |
| 85 | */ | |
| 86 | @SuppressWarnings("SameParameterValue") | |
| 87 | private String escape( | |
| 88 | final String haystack, final char needle, final String thread ) { | |
| 89 | int end = haystack.indexOf( needle ); | |
| 90 | ||
| 91 | if( end < 0 ) { | |
| 92 | return haystack; | |
| 93 | } | |
| 94 | ||
| 95 | final int length = haystack.length(); | |
| 96 | int start = 0; | |
| 97 | ||
| 98 | // Replace up to 32 occurrences before the string reallocates its buffer. | |
| 99 | final StringBuilder sb = new StringBuilder( length + 32 ); | |
| 100 | ||
| 101 | while( end >= 0 ) { | |
| 102 | sb.append( haystack, start, end ).append( thread ); | |
| 103 | start = end + 1; | |
| 104 | end = haystack.indexOf( needle, start ); | |
| 105 | } | |
| 106 | ||
| 107 | return sb.append( haystack.substring( start ) ).toString(); | |
| 108 | } | |
| 109 | } | |
| 1 | 110 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.service.Snitch; | |
| 32 | import net.sf.saxon.TransformerFactoryImpl; | |
| 33 | import net.sf.saxon.trans.XPathException; | |
| 34 | ||
| 35 | import javax.xml.stream.XMLEventReader; | |
| 36 | import javax.xml.stream.XMLInputFactory; | |
| 37 | import javax.xml.stream.XMLStreamException; | |
| 38 | import javax.xml.stream.events.ProcessingInstruction; | |
| 39 | import javax.xml.stream.events.XMLEvent; | |
| 40 | import javax.xml.transform.*; | |
| 41 | import javax.xml.transform.stream.StreamResult; | |
| 42 | import javax.xml.transform.stream.StreamSource; | |
| 43 | import java.io.File; | |
| 44 | import java.io.Reader; | |
| 45 | import java.io.StringReader; | |
| 46 | import java.io.StringWriter; | |
| 47 | import java.nio.file.Path; | |
| 48 | import java.nio.file.Paths; | |
| 49 | ||
| 50 | import static net.sf.saxon.tree.util.ProcInstParser.getPseudoAttribute; | |
| 51 | ||
| 52 | /** | |
| 53 | * Transforms an XML document. The XML document must have a stylesheet specified | |
| 54 | * as part of its processing instructions, such as: | |
| 55 | * <p> | |
| 56 | * {@code xml-stylesheet type="text/xsl" href="markdown.xsl"} | |
| 57 | * </p> | |
| 58 | * <p> | |
| 59 | * The XSL must transform the XML document into Markdown, or another format | |
| 60 | * recognized by the next link on the chain. | |
| 61 | * </p> | |
| 62 | */ | |
| 63 | public class XmlProcessor extends AbstractProcessor<String> | |
| 64 | implements ErrorListener { | |
| 65 | ||
| 66 | private final Snitch snitch = Services.load( Snitch.class ); | |
| 67 | ||
| 68 | private XMLInputFactory xmlInputFactory; | |
| 69 | private TransformerFactory transformerFactory; | |
| 70 | private Transformer transformer; | |
| 71 | ||
| 72 | private Path path; | |
| 73 | ||
| 74 | /** | |
| 75 | * Constructs an XML processor that can transform an XML document into another | |
| 76 | * format based on the XSL file specified as a processing instruction. The | |
| 77 | * path must point to the directory where the XSL file is found, which implies | |
| 78 | * that they must be in the same directory. | |
| 79 | * | |
| 80 | * @param processor Next link in the processing chain. | |
| 81 | * @param path The path to the XML file content to be processed. | |
| 82 | */ | |
| 83 | public XmlProcessor( final Processor<String> processor, final Path path ) { | |
| 84 | super( processor ); | |
| 85 | setPath( path ); | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Transforms the given XML text into another form (typically Markdown). | |
| 90 | * | |
| 91 | * @param text The text to transform, can be empty, cannot be null. | |
| 92 | * @return The transformed text, or empty if text is empty. | |
| 93 | */ | |
| 94 | @Override | |
| 95 | public String apply( final String text ) { | |
| 96 | try { | |
| 97 | return text.isEmpty() ? text : transform( text ); | |
| 98 | } catch( final Exception ex ) { | |
| 99 | throw new RuntimeException( ex ); | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Performs an XSL transformation on the given XML text. The XML text must | |
| 105 | * have a processing instruction that points to the XSL template file to use | |
| 106 | * for the transformation. | |
| 107 | * | |
| 108 | * @param text The text to transform. | |
| 109 | * @return The transformed text. | |
| 110 | */ | |
| 111 | private String transform( final String text ) throws Exception { | |
| 112 | // Extract the XML stylesheet processing instruction. | |
| 113 | final String template = getXsltFilename( text ); | |
| 114 | final Path xsl = getXslPath( template ); | |
| 115 | ||
| 116 | try( | |
| 117 | final StringWriter output = new StringWriter( text.length() ); | |
| 118 | final StringReader input = new StringReader( text ) ) { | |
| 119 | ||
| 120 | // Listen for external file modification events. | |
| 121 | getSnitch().listen( xsl ); | |
| 122 | ||
| 123 | getTransformer( xsl ).transform( | |
| 124 | new StreamSource( input ), | |
| 125 | new StreamResult( output ) | |
| 126 | ); | |
| 127 | ||
| 128 | return output.toString(); | |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Returns an XSL transformer ready to transform an XML document using the | |
| 134 | * XSLT file specified by the given path. If the path is already known then | |
| 135 | * this will return the associated transformer. | |
| 136 | * | |
| 137 | * @param xsl The path to an XSLT file. | |
| 138 | * @return A transformer that will transform XML documents using the given | |
| 139 | * XSLT file. | |
| 140 | * @throws TransformerConfigurationException Could not instantiate the | |
| 141 | * transformer. | |
| 142 | */ | |
| 143 | private Transformer getTransformer( final Path xsl ) | |
| 144 | throws TransformerConfigurationException { | |
| 145 | if( this.transformer == null ) { | |
| 146 | this.transformer = createTransformer( xsl ); | |
| 147 | } | |
| 148 | ||
| 149 | return this.transformer; | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Creates a configured transformer ready to run. | |
| 154 | * | |
| 155 | * @param xsl The stylesheet to use for transforming XML documents. | |
| 156 | * @return The edited XML document transformed into another format (usually | |
| 157 | * markdown). | |
| 158 | * @throws TransformerConfigurationException Could not create the transformer. | |
| 159 | */ | |
| 160 | protected Transformer createTransformer( final Path xsl ) | |
| 161 | throws TransformerConfigurationException { | |
| 162 | final Source xslt = new StreamSource( xsl.toFile() ); | |
| 163 | ||
| 164 | return getTransformerFactory().newTransformer( xslt ); | |
| 165 | } | |
| 166 | ||
| 167 | private Path getXslPath( final String filename ) { | |
| 168 | final Path xmlPath = getPath(); | |
| 169 | final File xmlDirectory = xmlPath.toFile().getParentFile(); | |
| 170 | ||
| 171 | return Paths.get( xmlDirectory.getPath(), filename ); | |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Given XML text, this will use a StAX pull reader to obtain the XML | |
| 176 | * stylesheet processing instruction. This will throw a parse exception if the | |
| 177 | * href pseudo-attribute filename value cannot be found. | |
| 178 | * | |
| 179 | * @param xml The XML containing an xml-stylesheet processing instruction. | |
| 180 | * @return The href pseudo-attribute value. | |
| 181 | * @throws XMLStreamException Could not parse the XML file. | |
| 182 | */ | |
| 183 | private String getXsltFilename( final String xml ) | |
| 184 | throws XMLStreamException, XPathException { | |
| 185 | ||
| 186 | String result = ""; | |
| 187 | ||
| 188 | try( final StringReader sr = new StringReader( xml ) ) { | |
| 189 | boolean found = false; | |
| 190 | int count = 0; | |
| 191 | final XMLEventReader reader = createXMLEventReader( sr ); | |
| 192 | ||
| 193 | // If the processing instruction wasn't found in the first 10 lines, | |
| 194 | // fail fast. This should iterate twice through the loop. | |
| 195 | while( !found && reader.hasNext() && count++ < 10 ) { | |
| 196 | final XMLEvent event = reader.nextEvent(); | |
| 197 | ||
| 198 | if( event.isProcessingInstruction() ) { | |
| 199 | final ProcessingInstruction pi = (ProcessingInstruction) event; | |
| 200 | final String target = pi.getTarget(); | |
| 201 | ||
| 202 | if( "xml-stylesheet".equalsIgnoreCase( target ) ) { | |
| 203 | result = getPseudoAttribute( pi.getData(), "href" ); | |
| 204 | found = true; | |
| 205 | } | |
| 206 | } | |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | return result; | |
| 211 | } | |
| 212 | ||
| 213 | private XMLEventReader createXMLEventReader( final Reader reader ) | |
| 214 | throws XMLStreamException { | |
| 215 | return getXMLInputFactory().createXMLEventReader( reader ); | |
| 216 | } | |
| 217 | ||
| 218 | private synchronized XMLInputFactory getXMLInputFactory() { | |
| 219 | if( this.xmlInputFactory == null ) { | |
| 220 | this.xmlInputFactory = createXMLInputFactory(); | |
| 221 | } | |
| 222 | ||
| 223 | return this.xmlInputFactory; | |
| 224 | } | |
| 225 | ||
| 226 | private XMLInputFactory createXMLInputFactory() { | |
| 227 | return XMLInputFactory.newInstance(); | |
| 228 | } | |
| 229 | ||
| 230 | private synchronized TransformerFactory getTransformerFactory() { | |
| 231 | if( this.transformerFactory == null ) { | |
| 232 | this.transformerFactory = createTransformerFactory(); | |
| 233 | } | |
| 234 | ||
| 235 | return this.transformerFactory; | |
| 236 | } | |
| 237 | ||
| 238 | /** | |
| 239 | * Returns a high-performance XSLT 2 transformation engine. | |
| 240 | * | |
| 241 | * @return An XSL transforming engine. | |
| 242 | */ | |
| 243 | private TransformerFactory createTransformerFactory() { | |
| 244 | final TransformerFactory factory = new TransformerFactoryImpl(); | |
| 245 | ||
| 246 | // Bubble problems up to the user interface, rather than standard error. | |
| 247 | factory.setErrorListener( this ); | |
| 248 | ||
| 249 | return factory; | |
| 250 | } | |
| 251 | ||
| 252 | /** | |
| 253 | * Called when the XSL transformer issues a warning. | |
| 254 | * | |
| 255 | * @param ex The problem the transformer encountered. | |
| 256 | */ | |
| 257 | @Override | |
| 258 | public void warning( final TransformerException ex ) { | |
| 259 | throw new RuntimeException( ex ); | |
| 260 | } | |
| 261 | ||
| 262 | /** | |
| 263 | * Called when the XSL transformer issues an error. | |
| 264 | * | |
| 265 | * @param ex The problem the transformer encountered. | |
| 266 | */ | |
| 267 | @Override | |
| 268 | public void error( final TransformerException ex ) { | |
| 269 | throw new RuntimeException( ex ); | |
| 270 | } | |
| 271 | ||
| 272 | /** | |
| 273 | * Called when the XSL transformer issues a fatal error, which is probably | |
| 274 | * a bit over-dramatic a method name. | |
| 275 | * | |
| 276 | * @param ex The problem the transformer encountered. | |
| 277 | */ | |
| 278 | @Override | |
| 279 | public void fatalError( final TransformerException ex ) { | |
| 280 | throw new RuntimeException( ex ); | |
| 281 | } | |
| 282 | ||
| 283 | private void setPath( final Path path ) { | |
| 284 | this.path = path; | |
| 285 | } | |
| 286 | ||
| 287 | private Path getPath() { | |
| 288 | return this.path; | |
| 289 | } | |
| 290 | ||
| 291 | private Snitch getSnitch() { | |
| 292 | return this.snitch; | |
| 293 | } | |
| 294 | } | |
| 1 | 295 |
| 1 | package com.scrivenvar.processors.markdown; | |
| 2 | ||
| 3 | import com.vladsch.flexmark.ast.BlockQuote; | |
| 4 | import com.vladsch.flexmark.ast.ListBlock; | |
| 5 | import com.vladsch.flexmark.html.AttributeProvider; | |
| 6 | import com.vladsch.flexmark.html.AttributeProviderFactory; | |
| 7 | import com.vladsch.flexmark.html.IndependentAttributeProviderFactory; | |
| 8 | import com.vladsch.flexmark.html.renderer.AttributablePart; | |
| 9 | import com.vladsch.flexmark.html.renderer.LinkResolverContext; | |
| 10 | import com.vladsch.flexmark.util.ast.Block; | |
| 11 | import com.vladsch.flexmark.util.ast.Node; | |
| 12 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 13 | import com.vladsch.flexmark.util.html.MutableAttributes; | |
| 14 | import org.jetbrains.annotations.NotNull; | |
| 15 | ||
| 16 | import static com.scrivenvar.Constants.PARAGRAPH_ID_PREFIX; | |
| 17 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 18 | import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension; | |
| 19 | import static com.vladsch.flexmark.html.renderer.CoreNodeRenderer.CODE_CONTENT; | |
| 20 | ||
| 21 | /** | |
| 22 | * Responsible for giving most block-level elements a unique identifier | |
| 23 | * attribute. The identifier is used to coordinate scrolling. | |
| 24 | */ | |
| 25 | public class BlockExtension implements HtmlRendererExtension { | |
| 26 | /** | |
| 27 | * Responsible for creating the id attribute. This class is instantiated | |
| 28 | * each time the document is rendered, thereby resetting the count to zero. | |
| 29 | */ | |
| 30 | public static class IdAttributeProvider implements AttributeProvider { | |
| 31 | private int mCount; | |
| 32 | ||
| 33 | private static AttributeProviderFactory createFactory() { | |
| 34 | return new IndependentAttributeProviderFactory() { | |
| 35 | @Override | |
| 36 | public @NotNull AttributeProvider apply( | |
| 37 | @NotNull final LinkResolverContext context ) { | |
| 38 | return new IdAttributeProvider(); | |
| 39 | } | |
| 40 | }; | |
| 41 | } | |
| 42 | ||
| 43 | @Override | |
| 44 | public void setAttributes( @NotNull Node node, | |
| 45 | @NotNull AttributablePart part, | |
| 46 | @NotNull MutableAttributes attributes ) { | |
| 47 | // Blockquotes are troublesome because they can interleave blank lines | |
| 48 | // without having an equivalent blank line in the source document. That | |
| 49 | // is, in Markdown the > symbol on a line by itself will generate a blank | |
| 50 | // line in the resulting document; however, a > symbol in the text editor | |
| 51 | // does not count as a blank line. Resolving this issue is tricky. | |
| 52 | // | |
| 53 | // The CODE_CONTENT represents <code> embedded inside <pre>; both elements | |
| 54 | // enter this method as FencedCodeBlock, but only the <pre> must be | |
| 55 | // uniquely identified (because they are the same line in Markdown). | |
| 56 | // | |
| 57 | if( node instanceof Block && | |
| 58 | !(node instanceof BlockQuote) && | |
| 59 | !(node instanceof ListBlock) && | |
| 60 | (part != CODE_CONTENT) ) { | |
| 61 | attributes.addValue( "id", PARAGRAPH_ID_PREFIX + mCount++ ); | |
| 62 | } | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | private BlockExtension() { | |
| 67 | } | |
| 68 | ||
| 69 | @Override | |
| 70 | public void extend( final Builder builder, | |
| 71 | @NotNull final String rendererType ) { | |
| 72 | builder.attributeProviderFactory( IdAttributeProvider.createFactory() ); | |
| 73 | } | |
| 74 | ||
| 75 | public static BlockExtension create() { | |
| 76 | return new BlockExtension(); | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | public void rendererOptions( @NotNull final MutableDataHolder options ) { | |
| 81 | } | |
| 82 | } | |
| 1 | 83 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.preferences.UserPreferences; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | import com.vladsch.flexmark.ast.Image; | |
| 34 | import com.vladsch.flexmark.html.IndependentLinkResolverFactory; | |
| 35 | import com.vladsch.flexmark.html.LinkResolver; | |
| 36 | import com.vladsch.flexmark.html.renderer.LinkResolverBasicContext; | |
| 37 | import com.vladsch.flexmark.html.renderer.LinkStatus; | |
| 38 | import com.vladsch.flexmark.html.renderer.ResolvedLink; | |
| 39 | import com.vladsch.flexmark.util.ast.Node; | |
| 40 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 41 | import org.jetbrains.annotations.NotNull; | |
| 42 | import org.renjin.repackaged.guava.base.Splitter; | |
| 43 | ||
| 44 | import java.io.File; | |
| 45 | import java.io.FileNotFoundException; | |
| 46 | import java.nio.file.Path; | |
| 47 | ||
| 48 | import static com.scrivenvar.StatusBarNotifier.alert; | |
| 49 | import static com.scrivenvar.util.ProtocolResolver.getProtocol; | |
| 50 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 51 | import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension; | |
| 52 | import static java.lang.String.format; | |
| 53 | ||
| 54 | /** | |
| 55 | * Responsible for ensuring that images can be rendered relative to a path. | |
| 56 | * This allows images to be located virtually anywhere. | |
| 57 | */ | |
| 58 | public class ImageLinkExtension implements HtmlRendererExtension { | |
| 59 | /** | |
| 60 | * Used for image directory preferences. | |
| 61 | */ | |
| 62 | private static final Options sOptions = Services.load( Options.class ); | |
| 63 | ||
| 64 | /** | |
| 65 | * Creates an extension capable of using a relative path to embed images. | |
| 66 | * | |
| 67 | * @param path The {@link Path} to the file being edited; the parent path | |
| 68 | * is the starting location of the relative image directory. | |
| 69 | * @return The new {@link ImageLinkExtension}, never {@code null}. | |
| 70 | */ | |
| 71 | public static ImageLinkExtension create( @NotNull final Path path ) { | |
| 72 | return new ImageLinkExtension( path ); | |
| 73 | } | |
| 74 | ||
| 75 | private class Factory extends IndependentLinkResolverFactory { | |
| 76 | @Override | |
| 77 | public @NotNull LinkResolver apply( | |
| 78 | @NotNull final LinkResolverBasicContext context ) { | |
| 79 | return new ImageLinkResolver(); | |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | private class ImageLinkResolver implements LinkResolver { | |
| 84 | private final UserPreferences mUserPref = getUserPreferences(); | |
| 85 | private final File mImagesUserPrefix = mUserPref.getImagesDirectory(); | |
| 86 | private final String mImageExtensions = mUserPref.getImagesOrder(); | |
| 87 | ||
| 88 | public ImageLinkResolver() { | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * You can also set/clear/modify attributes through | |
| 93 | * {@link ResolvedLink#getAttributes()} and | |
| 94 | * {@link ResolvedLink#getNonNullAttributes()}. | |
| 95 | */ | |
| 96 | @NotNull | |
| 97 | @Override | |
| 98 | public ResolvedLink resolveLink( | |
| 99 | @NotNull final Node node, | |
| 100 | @NotNull final LinkResolverBasicContext context, | |
| 101 | @NotNull final ResolvedLink link ) { | |
| 102 | return node instanceof Image ? resolve( link ) : link; | |
| 103 | } | |
| 104 | ||
| 105 | private ResolvedLink resolve( final ResolvedLink link ) { | |
| 106 | var url = link.getUrl(); | |
| 107 | final var protocol = getProtocol( url ); | |
| 108 | ||
| 109 | try { | |
| 110 | // If the direct file name exists, then use it directly. | |
| 111 | if( (protocol.isFile() && Path.of( url ).toFile().exists()) || | |
| 112 | protocol.isHttp() ) { | |
| 113 | return valid( link, url ); | |
| 114 | } | |
| 115 | } catch( final Exception ignored ) { | |
| 116 | // Try to resolve the image, dynamically. | |
| 117 | } | |
| 118 | ||
| 119 | try { | |
| 120 | final Path imagePrefix = getImagePrefix().toPath(); | |
| 121 | ||
| 122 | // Path to the file being edited. | |
| 123 | Path editPath = getEditPath(); | |
| 124 | ||
| 125 | // If there is no parent path to the file, it means the file has not | |
| 126 | // been saved. Default to using the value from the user's preferences. | |
| 127 | // The user's preferences will be defaulted to a the application's | |
| 128 | // starting directory. | |
| 129 | if( editPath == null ) { | |
| 130 | editPath = imagePrefix; | |
| 131 | } | |
| 132 | else { | |
| 133 | editPath = Path.of( editPath.toString(), imagePrefix.toString() ); | |
| 134 | } | |
| 135 | ||
| 136 | final Path imagePathPrefix = Path.of( editPath.toString(), url ); | |
| 137 | final String suffixes = getImageExtensions(); | |
| 138 | boolean missing = true; | |
| 139 | ||
| 140 | // Iterate over the user's preferred image file type extensions. | |
| 141 | for( final String ext : Splitter.on( ' ' ).split( suffixes ) ) { | |
| 142 | final String imagePath = format( "%s.%s", imagePathPrefix, ext ); | |
| 143 | final File file = new File( imagePath ); | |
| 144 | ||
| 145 | if( file.exists() ) { | |
| 146 | url = file.toString(); | |
| 147 | missing = false; | |
| 148 | break; | |
| 149 | } | |
| 150 | } | |
| 151 | ||
| 152 | if( missing ) { | |
| 153 | throw new FileNotFoundException( imagePathPrefix + ".*" ); | |
| 154 | } | |
| 155 | ||
| 156 | if( protocol.isFile() ) { | |
| 157 | url = "file://" + url; | |
| 158 | } | |
| 159 | ||
| 160 | return valid( link, url ); | |
| 161 | } catch( final Exception ex ) { | |
| 162 | alert( ex ); | |
| 163 | } | |
| 164 | ||
| 165 | return link; | |
| 166 | } | |
| 167 | ||
| 168 | private ResolvedLink valid( final ResolvedLink link, final String url ) { | |
| 169 | return link.withStatus( LinkStatus.VALID ).withUrl( url ); | |
| 170 | } | |
| 171 | ||
| 172 | private File getImagePrefix() { | |
| 173 | return mImagesUserPrefix; | |
| 174 | } | |
| 175 | ||
| 176 | private String getImageExtensions() { | |
| 177 | return mImageExtensions; | |
| 178 | } | |
| 179 | ||
| 180 | private Path getEditPath() { | |
| 181 | return mPath.getParent(); | |
| 182 | } | |
| 183 | } | |
| 184 | ||
| 185 | private final Path mPath; | |
| 186 | ||
| 187 | private ImageLinkExtension( @NotNull final Path path ) { | |
| 188 | mPath = path; | |
| 189 | } | |
| 190 | ||
| 191 | @Override | |
| 192 | public void rendererOptions( @NotNull final MutableDataHolder options ) { | |
| 193 | } | |
| 194 | ||
| 195 | @Override | |
| 196 | public void extend( @NotNull final Builder builder, | |
| 197 | @NotNull final String rendererType ) { | |
| 198 | builder.linkResolverFactory( new Factory() ); | |
| 199 | } | |
| 200 | ||
| 201 | private UserPreferences getUserPreferences() { | |
| 202 | return getOptions().getUserPreferences(); | |
| 203 | } | |
| 204 | ||
| 205 | private static Options getOptions() { | |
| 206 | return sOptions; | |
| 207 | } | |
| 208 | } | |
| 1 | 209 |
| 1 | package com.scrivenvar.processors.markdown; | |
| 2 | ||
| 3 | import com.vladsch.flexmark.ast.Text; | |
| 4 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 5 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 6 | import com.vladsch.flexmark.html.renderer.NodeRendererContext; | |
| 7 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; | |
| 9 | import com.vladsch.flexmark.util.ast.TextCollectingVisitor; | |
| 10 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 11 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 12 | import org.jetbrains.annotations.NotNull; | |
| 13 | import org.jetbrains.annotations.Nullable; | |
| 14 | ||
| 15 | import java.util.LinkedHashMap; | |
| 16 | import java.util.Map; | |
| 17 | import java.util.Set; | |
| 18 | ||
| 19 | import static com.scrivenvar.processors.text.TextReplacementFactory.replace; | |
| 20 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 21 | import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension; | |
| 22 | ||
| 23 | /** | |
| 24 | * Responsible for substituting multi-codepoint glyphs with single codepoint | |
| 25 | * glyphs. The text is adorned with ligatures prior to rendering as HTML. | |
| 26 | * This requires a font that supports ligatures. | |
| 27 | * <p> | |
| 28 | * TODO: I18N https://github.com/DaveJarvis/scrivenvar/issues/81 | |
| 29 | * </p> | |
| 30 | */ | |
| 31 | public class LigatureExtension implements HtmlRendererExtension { | |
| 32 | /** | |
| 33 | * Retain insertion order so that ligature substitution uses longer ligatures | |
| 34 | * ahead of shorter ligatures. The word "ruffian" should use the "ffi" | |
| 35 | * ligature, not the "ff" ligature. | |
| 36 | */ | |
| 37 | private static final Map<String, String> LIGATURES = new LinkedHashMap<>(); | |
| 38 | ||
| 39 | static { | |
| 40 | LIGATURES.put( "ffi", "\uFB03" ); | |
| 41 | LIGATURES.put( "ffl", "\uFB04" ); | |
| 42 | LIGATURES.put( "ff", "\uFB00" ); | |
| 43 | LIGATURES.put( "fi", "\uFB01" ); | |
| 44 | LIGATURES.put( "fl", "\uFB02" ); | |
| 45 | LIGATURES.put( "ft", "\uFB05" ); | |
| 46 | LIGATURES.put( "AE", "\u00C6" ); | |
| 47 | LIGATURES.put( "OE", "\u0152" ); | |
| 48 | // "ae", "\u00E6", | |
| 49 | // "oe", "\u0153", | |
| 50 | } | |
| 51 | ||
| 52 | private static class LigatureRenderer implements NodeRenderer { | |
| 53 | private final TextCollectingVisitor mVisitor = new TextCollectingVisitor(); | |
| 54 | ||
| 55 | @SuppressWarnings("unused") | |
| 56 | public LigatureRenderer( final DataHolder options ) { | |
| 57 | } | |
| 58 | ||
| 59 | @Override | |
| 60 | public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 61 | return Set.of( new NodeRenderingHandler<>( | |
| 62 | Text.class, LigatureRenderer.this::render ) ); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * This will pick the fastest string replacement algorithm based on the | |
| 67 | * text length. The insertion order of the {@link #LIGATURES} is | |
| 68 | * important to give precedence to longer ligatures. | |
| 69 | * | |
| 70 | * @param textNode The text node containing text to replace with ligatures. | |
| 71 | * @param context Not used. | |
| 72 | * @param html Where to write the text adorned with ligatures. | |
| 73 | */ | |
| 74 | private void render( | |
| 75 | @NotNull final Text textNode, | |
| 76 | @NotNull final NodeRendererContext context, | |
| 77 | @NotNull final HtmlWriter html ) { | |
| 78 | final var text = mVisitor.collectAndGetText( textNode ); | |
| 79 | html.text( replace( text, LIGATURES ) ); | |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | private static class Factory implements NodeRendererFactory { | |
| 84 | @NotNull | |
| 85 | @Override | |
| 86 | public NodeRenderer apply( @NotNull DataHolder options ) { | |
| 87 | return new LigatureRenderer( options ); | |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | private LigatureExtension() { | |
| 92 | } | |
| 93 | ||
| 94 | @Override | |
| 95 | public void rendererOptions( @NotNull final MutableDataHolder options ) { | |
| 96 | } | |
| 97 | ||
| 98 | @Override | |
| 99 | public void extend( @NotNull final Builder builder, | |
| 100 | @NotNull final String rendererType ) { | |
| 101 | if( "HTML".equalsIgnoreCase( rendererType ) ) { | |
| 102 | builder.nodeRendererFactory( new Factory() ); | |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | public static LigatureExtension create() { | |
| 107 | return new LigatureExtension(); | |
| 108 | } | |
| 109 | } | |
| 1 | 110 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.processors.AbstractProcessor; | |
| 31 | import com.scrivenvar.processors.Processor; | |
| 32 | import com.vladsch.flexmark.ext.definition.DefinitionExtension; | |
| 33 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; | |
| 34 | import com.vladsch.flexmark.ext.superscript.SuperscriptExtension; | |
| 35 | import com.vladsch.flexmark.ext.tables.TablesExtension; | |
| 36 | import com.vladsch.flexmark.ext.typographic.TypographicExtension; | |
| 37 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 38 | import com.vladsch.flexmark.parser.Parser; | |
| 39 | import com.vladsch.flexmark.util.ast.IParse; | |
| 40 | import com.vladsch.flexmark.util.ast.Node; | |
| 41 | import com.vladsch.flexmark.util.misc.Extension; | |
| 42 | ||
| 43 | import java.nio.file.Path; | |
| 44 | import java.util.ArrayList; | |
| 45 | import java.util.Collection; | |
| 46 | ||
| 47 | import static com.scrivenvar.Constants.USER_DIRECTORY; | |
| 48 | ||
| 49 | /** | |
| 50 | * Responsible for parsing a Markdown document and rendering it as HTML. | |
| 51 | */ | |
| 52 | public class MarkdownProcessor extends AbstractProcessor<String> { | |
| 53 | ||
| 54 | private final HtmlRenderer mRenderer; | |
| 55 | private final IParse mParser; | |
| 56 | ||
| 57 | public MarkdownProcessor( | |
| 58 | final Processor<String> successor ) { | |
| 59 | this( successor, Path.of( USER_DIRECTORY ) ); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Constructs a new Markdown processor that can create HTML documents. | |
| 64 | * | |
| 65 | * @param successor Usually the HTML Preview Processor. | |
| 66 | */ | |
| 67 | public MarkdownProcessor( | |
| 68 | final Processor<String> successor, final Path path ) { | |
| 69 | super( successor ); | |
| 70 | ||
| 71 | // Standard extensions | |
| 72 | final Collection<Extension> extensions = new ArrayList<>(); | |
| 73 | extensions.add( DefinitionExtension.create() ); | |
| 74 | extensions.add( StrikethroughSubscriptExtension.create() ); | |
| 75 | extensions.add( SuperscriptExtension.create() ); | |
| 76 | extensions.add( TablesExtension.create() ); | |
| 77 | extensions.add( TypographicExtension.create() ); | |
| 78 | ||
| 79 | // Allows referencing image files via relative paths and dynamic file types. | |
| 80 | extensions.add( ImageLinkExtension.create( path ) ); | |
| 81 | extensions.add( BlockExtension.create() ); | |
| 82 | extensions.add( TeXExtension.create() ); | |
| 83 | ||
| 84 | // TODO: https://github.com/FAlthausen/Vollkorn-Typeface/issues/38 | |
| 85 | // TODO: Uncomment when Vollkorn ligatures are fixed. | |
| 86 | // extensions.add( LigatureExtension.create() ); | |
| 87 | ||
| 88 | mRenderer = HtmlRenderer.builder().extensions( extensions ).build(); | |
| 89 | mParser = Parser.builder() | |
| 90 | .extensions( extensions ) | |
| 91 | .build(); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Converts the given Markdown string into HTML, without the doctype, html, | |
| 96 | * head, and body tags. | |
| 97 | * | |
| 98 | * @param markdown The string to convert from Markdown to HTML. | |
| 99 | * @return The HTML representation of the Markdown document. | |
| 100 | */ | |
| 101 | @Override | |
| 102 | public String apply( final String markdown ) { | |
| 103 | return toHtml( markdown ); | |
| 104 | } | |
| 105 | ||
| 106 | /** | |
| 107 | * Returns the AST in the form of a node for the given markdown document. This | |
| 108 | * can be used, for example, to determine if a hyperlink exists inside of a | |
| 109 | * paragraph. | |
| 110 | * | |
| 111 | * @param markdown The markdown to convert into an AST. | |
| 112 | * @return The markdown AST for the given text (usually a paragraph). | |
| 113 | */ | |
| 114 | public Node toNode( final String markdown ) { | |
| 115 | return parse( markdown ); | |
| 116 | } | |
| 117 | ||
| 118 | /** | |
| 119 | * Helper method to create an AST given some markdown. | |
| 120 | * | |
| 121 | * @param markdown The markdown to parse. | |
| 122 | * @return The root node of the markdown tree. | |
| 123 | */ | |
| 124 | private Node parse( final String markdown ) { | |
| 125 | return getParser().parse( markdown ); | |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Converts a string of markdown into HTML. | |
| 130 | * | |
| 131 | * @param markdown The markdown text to convert to HTML, must not be null. | |
| 132 | * @return The markdown rendered as an HTML document. | |
| 133 | */ | |
| 134 | private String toHtml( final String markdown ) { | |
| 135 | return getRenderer().render( parse( markdown ) ); | |
| 136 | } | |
| 137 | ||
| 138 | /** | |
| 139 | * Creates the Markdown document processor. | |
| 140 | * | |
| 141 | * @return A Parser that can build an abstract syntax tree. | |
| 142 | */ | |
| 143 | private IParse getParser() { | |
| 144 | return mParser; | |
| 145 | } | |
| 146 | ||
| 147 | private HtmlRenderer getRenderer() { | |
| 148 | return mRenderer; | |
| 149 | } | |
| 150 | } | |
| 1 | 151 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown; | |
| 29 | ||
| 30 | import com.scrivenvar.processors.markdown.tex.TeXInlineDelimiterProcessor; | |
| 31 | import com.scrivenvar.processors.markdown.tex.TeXNodeRenderer; | |
| 32 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 33 | import com.vladsch.flexmark.parser.Parser; | |
| 34 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 35 | import org.jetbrains.annotations.NotNull; | |
| 36 | ||
| 37 | import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension; | |
| 38 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 39 | ||
| 40 | /** | |
| 41 | * Responsible for wrapping delimited TeX code in Markdown into an XML element | |
| 42 | * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes | |
| 43 | * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer | |
| 44 | * is responsible for converting the TeX code for display. This avoids inserting | |
| 45 | * SVG code into the Markdown document, which the parser would then have to | |
| 46 | * iterate---a <em>very</em> wasteful operation that impacts front-end | |
| 47 | * performance. | |
| 48 | */ | |
| 49 | public class TeXExtension implements ParserExtension, HtmlRendererExtension { | |
| 50 | /** | |
| 51 | * Creates an extension capable of handling delimited TeX code in Markdown. | |
| 52 | * | |
| 53 | * @return The new {@link TeXExtension}, never {@code null}. | |
| 54 | */ | |
| 55 | public static TeXExtension create() { | |
| 56 | return new TeXExtension(); | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * Force using the {@link #create()} method for consistency. | |
| 61 | */ | |
| 62 | private TeXExtension() { | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Adds the TeX extension for HTML document export types. | |
| 67 | * | |
| 68 | * @param builder The document builder. | |
| 69 | * @param rendererType Indicates the document type to be built. | |
| 70 | */ | |
| 71 | @Override | |
| 72 | public void extend( @NotNull final HtmlRenderer.Builder builder, | |
| 73 | @NotNull final String rendererType ) { | |
| 74 | if( "HTML".equalsIgnoreCase( rendererType ) ) { | |
| 75 | builder.nodeRendererFactory( new TeXNodeRenderer.Factory() ); | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | public void extend( final Parser.Builder builder ) { | |
| 81 | builder.customDelimiterProcessor( new TeXInlineDelimiterProcessor() ); | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | public void rendererOptions( @NotNull final MutableDataHolder options ) { | |
| 86 | } | |
| 87 | ||
| 88 | @Override | |
| 89 | public void parserOptions( final MutableDataHolder options ) { | |
| 90 | } | |
| 91 | } | |
| 1 | 92 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown.tex; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.parser.InlineParser; | |
| 31 | import com.vladsch.flexmark.parser.core.delimiter.Delimiter; | |
| 32 | import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor; | |
| 33 | import com.vladsch.flexmark.parser.delimiter.DelimiterRun; | |
| 34 | import com.vladsch.flexmark.util.ast.Node; | |
| 35 | ||
| 36 | public class TeXInlineDelimiterProcessor implements DelimiterProcessor { | |
| 37 | ||
| 38 | @Override | |
| 39 | public void process( final Delimiter opener, final Delimiter closer, | |
| 40 | final int delimitersUsed ) { | |
| 41 | final var node = new TeXNode(); | |
| 42 | opener.moveNodesBetweenDelimitersTo(node, closer); | |
| 43 | } | |
| 44 | ||
| 45 | @Override | |
| 46 | public char getOpeningCharacter() { | |
| 47 | return '$'; | |
| 48 | } | |
| 49 | ||
| 50 | @Override | |
| 51 | public char getClosingCharacter() { | |
| 52 | return '$'; | |
| 53 | } | |
| 54 | ||
| 55 | @Override | |
| 56 | public int getMinLength() { | |
| 57 | return 1; | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Allow for $ or $$. | |
| 62 | * | |
| 63 | * @param opener One or more opening delimiter characters. | |
| 64 | * @param closer One or more closing delimiter characters. | |
| 65 | * @return The number of delimiters to use to determine whether a valid | |
| 66 | * opening delimiter expression is found. | |
| 67 | */ | |
| 68 | @Override | |
| 69 | public int getDelimiterUse( | |
| 70 | final DelimiterRun opener, final DelimiterRun closer ) { | |
| 71 | return 1; | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public boolean canBeOpener( final String before, | |
| 76 | final String after, | |
| 77 | final boolean leftFlanking, | |
| 78 | final boolean rightFlanking, | |
| 79 | final boolean beforeIsPunctuation, | |
| 80 | final boolean afterIsPunctuation, | |
| 81 | final boolean beforeIsWhitespace, | |
| 82 | final boolean afterIsWhiteSpace ) { | |
| 83 | return leftFlanking; | |
| 84 | } | |
| 85 | ||
| 86 | @Override | |
| 87 | public boolean canBeCloser( final String before, | |
| 88 | final String after, | |
| 89 | final boolean leftFlanking, | |
| 90 | final boolean rightFlanking, | |
| 91 | final boolean beforeIsPunctuation, | |
| 92 | final boolean afterIsPunctuation, | |
| 93 | final boolean beforeIsWhitespace, | |
| 94 | final boolean afterIsWhiteSpace ) { | |
| 95 | return rightFlanking; | |
| 96 | } | |
| 97 | ||
| 98 | @Override | |
| 99 | public Node unmatchedDelimiterNode( | |
| 100 | final InlineParser inlineParser, final DelimiterRun delimiter ) { | |
| 101 | return null; | |
| 102 | } | |
| 103 | ||
| 104 | @Override | |
| 105 | public boolean skipNonOpenerCloser() { | |
| 106 | return false; | |
| 107 | } | |
| 108 | } | |
| 1 | 109 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown.tex; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.DelimitedNodeImpl; | |
| 31 | ||
| 32 | public class TeXNode extends DelimitedNodeImpl { | |
| 33 | ||
| 34 | public TeXNode() { | |
| 35 | } | |
| 36 | ||
| 37 | } | |
| 1 | 38 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.markdown.tex; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 31 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 32 | import com.vladsch.flexmark.html.renderer.NodeRendererContext; | |
| 33 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 34 | import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; | |
| 35 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 36 | import org.jetbrains.annotations.NotNull; | |
| 37 | import org.jetbrains.annotations.Nullable; | |
| 38 | ||
| 39 | import java.util.HashSet; | |
| 40 | import java.util.Set; | |
| 41 | ||
| 42 | public class TeXNodeRenderer implements NodeRenderer { | |
| 43 | ||
| 44 | public static class Factory implements NodeRendererFactory { | |
| 45 | @NotNull | |
| 46 | @Override | |
| 47 | public NodeRenderer apply( @NotNull DataHolder options ) { | |
| 48 | return new TeXNodeRenderer(); | |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | @Override | |
| 53 | public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 54 | final Set<NodeRenderingHandler<?>> set = new HashSet<>(); | |
| 55 | set.add( new NodeRenderingHandler<>( | |
| 56 | TeXNode.class, this::render ) ); | |
| 57 | ||
| 58 | return set; | |
| 59 | } | |
| 60 | ||
| 61 | private void render( final TeXNode node, | |
| 62 | final NodeRendererContext context, | |
| 63 | final HtmlWriter html ) { | |
| 64 | html.tag( "tex" ); | |
| 65 | html.raw( node.getText() ); | |
| 66 | html.closeTag( "tex" ); | |
| 67 | } | |
| 68 | } | |
| 1 | 69 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for common behaviour across all text replacer implementations. | |
| 34 | */ | |
| 35 | public abstract class AbstractTextReplacer implements TextReplacer { | |
| 36 | ||
| 37 | /** | |
| 38 | * Default (empty) constructor. | |
| 39 | */ | |
| 40 | protected AbstractTextReplacer() { | |
| 41 | } | |
| 42 | ||
| 43 | protected String[] keys( final Map<String, String> map ) { | |
| 44 | return map.keySet().toArray( new String[ 0 ] ); | |
| 45 | } | |
| 46 | ||
| 47 | protected String[] values( final Map<String, String> map ) { | |
| 48 | return map.values().toArray( new String[ 0 ] ); | |
| 49 | } | |
| 50 | } | |
| 1 | 51 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | import org.ahocorasick.trie.Emit; | |
| 32 | import org.ahocorasick.trie.Trie.TrieBuilder; | |
| 33 | import static org.ahocorasick.trie.Trie.builder; | |
| 34 | ||
| 35 | /** | |
| 36 | * Replaces text using an Aho-Corasick algorithm. | |
| 37 | */ | |
| 38 | public class AhoCorasickReplacer extends AbstractTextReplacer { | |
| 39 | ||
| 40 | /** | |
| 41 | * Default (empty) constructor. | |
| 42 | */ | |
| 43 | protected AhoCorasickReplacer() { | |
| 44 | } | |
| 45 | ||
| 46 | @Override | |
| 47 | public String replace( final String text, final Map<String, String> map ) { | |
| 48 | // Create a buffer sufficiently large that re-allocations are minimized. | |
| 49 | final StringBuilder sb = new StringBuilder( (int)(text.length() * 1.25) ); | |
| 50 | ||
| 51 | // The TrieBuilder should only match whole words and ignore overlaps (there | |
| 52 | // shouldn't be any). | |
| 53 | final TrieBuilder builder = builder().onlyWholeWords().ignoreOverlaps(); | |
| 54 | ||
| 55 | for( final String key : keys( map ) ) { | |
| 56 | builder.addKeyword( key ); | |
| 57 | } | |
| 58 | ||
| 59 | int index = 0; | |
| 60 | ||
| 61 | // Replace all instances with dereferenced variables. | |
| 62 | for( final Emit emit : builder.build().parseText( text ) ) { | |
| 63 | sb.append( text, index, emit.getStart() ); | |
| 64 | sb.append( map.get( emit.getKeyword() ) ); | |
| 65 | index = emit.getEnd() + 1; | |
| 66 | } | |
| 67 | ||
| 68 | // Add the remainder of the string (contains no more matches). | |
| 69 | sb.append( text.substring( index ) ); | |
| 70 | ||
| 71 | return sb.toString(); | |
| 72 | } | |
| 73 | } | |
| 1 | 74 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | import static org.apache.commons.lang3.StringUtils.replaceEach; | |
| 33 | ||
| 34 | /** | |
| 35 | * Replaces text using Apache's StringUtils.replaceEach method. | |
| 36 | */ | |
| 37 | public class StringUtilsReplacer extends AbstractTextReplacer { | |
| 38 | ||
| 39 | /** | |
| 40 | * Default (empty) constructor. | |
| 41 | */ | |
| 42 | protected StringUtilsReplacer() { | |
| 43 | } | |
| 44 | ||
| 45 | @Override | |
| 46 | public String replace( final String text, final Map<String, String> map ) { | |
| 47 | return replaceEach( text, keys( map ), values( map ) ); | |
| 48 | } | |
| 49 | } | |
| 1 | 50 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | /** | |
| 33 | * Used to generate a class capable of efficiently replacing variable | |
| 34 | * definitions with their values. | |
| 35 | */ | |
| 36 | public final class TextReplacementFactory { | |
| 37 | ||
| 38 | private static final TextReplacer APACHE = new StringUtilsReplacer(); | |
| 39 | private static final TextReplacer AHO_CORASICK = new AhoCorasickReplacer(); | |
| 40 | ||
| 41 | /** | |
| 42 | * Returns a text search/replacement instance that is reasonably optimal for | |
| 43 | * the given length of text. | |
| 44 | * | |
| 45 | * @param length The length of text that requires some search and replacing. | |
| 46 | * @return A class that can search and replace text with utmost expediency. | |
| 47 | */ | |
| 48 | public static TextReplacer getTextReplacer( final int length ) { | |
| 49 | // After about 1,500 characters, the StringUtils implementation is less | |
| 50 | // performant than the Aho-Corsick implementation. | |
| 51 | // | |
| 52 | // See http://stackoverflow.com/a/40836618/59087 | |
| 53 | return length < 1500 ? APACHE : AHO_CORASICK; | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Convenience method to instantiate a suitable text replacer algorithm and | |
| 58 | * perform a replacement using the given map. At this point, the values should | |
| 59 | * be already dereferenced and ready to be substituted verbatim; any | |
| 60 | * recursively defined values must have been interpolated previously. | |
| 61 | * | |
| 62 | * @param text The text containing zero or more variables to replace. | |
| 63 | * @param map The map of variables to their dereferenced values. | |
| 64 | * @return The text with all variables replaced. | |
| 65 | */ | |
| 66 | public static String replace( | |
| 67 | final String text, final Map<String, String> map ) { | |
| 68 | return getTextReplacer( text.length() ).replace( text, map ); | |
| 69 | } | |
| 70 | } | |
| 1 | 71 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.processors.text; | |
| 29 | ||
| 30 | import java.util.Map; | |
| 31 | ||
| 32 | /** | |
| 33 | * Defines the ability to replace text given a set of keys and values. | |
| 34 | */ | |
| 35 | public interface TextReplacer { | |
| 36 | ||
| 37 | /** | |
| 38 | * Searches through the given text for any of the keys given in the map and | |
| 39 | * replaces the keys that appear in the text with the key's corresponding | |
| 40 | * value. | |
| 41 | * | |
| 42 | * @param text The text that contains zero or more keys. | |
| 43 | * @param map The set of keys mapped to replacement values. | |
| 44 | * @return The given text with all keys replaced with corresponding values. | |
| 45 | */ | |
| 46 | String replace( String text, Map<String, String> map ); | |
| 47 | } | |
| 1 | 48 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | import com.scrivenvar.preferences.UserPreferences; | |
| 31 | ||
| 32 | import java.util.prefs.BackingStoreException; | |
| 33 | import java.util.prefs.Preferences; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for persisting options. | |
| 37 | */ | |
| 38 | public interface Options extends Service { | |
| 39 | ||
| 40 | /** | |
| 41 | * Returns a reference to the persistent settings that may be configured | |
| 42 | * through the UI. | |
| 43 | * | |
| 44 | * @return A valid {@link UserPreferences} instance, never {@code null}. | |
| 45 | */ | |
| 46 | UserPreferences getUserPreferences(); | |
| 47 | ||
| 48 | /** | |
| 49 | * Returns the {@link Preferences} that persist settings that cannot | |
| 50 | * be configured via the user interface. | |
| 51 | * | |
| 52 | * @return A valid {@link Preferences} instance, never {@code null}. | |
| 53 | */ | |
| 54 | Preferences getState(); | |
| 55 | ||
| 56 | /** | |
| 57 | * Stores the key and value into the user preferences to be loaded the next | |
| 58 | * time the application is launched. | |
| 59 | * | |
| 60 | * @param key Name of the key to persist along with its value. | |
| 61 | * @param value Value to associate with the key. | |
| 62 | * @throws BackingStoreException Could not persist the change. | |
| 63 | */ | |
| 64 | void put( String key, String value ) throws BackingStoreException; | |
| 65 | ||
| 66 | /** | |
| 67 | * Retrieves the value for a key in the user preferences. | |
| 68 | * | |
| 69 | * @param key Retrieve the value of this key. | |
| 70 | * @param defaultValue The value to return in the event that the given key has | |
| 71 | * no associated value. | |
| 72 | * @return The value associated with the key. | |
| 73 | */ | |
| 74 | String get( String key, String defaultValue ); | |
| 75 | ||
| 76 | /** | |
| 77 | * Retrieves the value for a key in the user preferences. This will return | |
| 78 | * the empty string if the value cannot be found. | |
| 79 | * | |
| 80 | * @param key The key to find in the preferences. | |
| 81 | * @return A non-null, possibly empty value for the key. | |
| 82 | */ | |
| 83 | String get( String key ); | |
| 84 | } | |
| 1 | 85 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | /** | |
| 31 | * All services inherit from this one. | |
| 32 | */ | |
| 33 | public interface Service { | |
| 34 | } | |
| 1 | 35 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | import java.util.Iterator; | |
| 31 | import java.util.List; | |
| 32 | ||
| 33 | /** | |
| 34 | * Defines how settings and options can be retrieved. | |
| 35 | */ | |
| 36 | public interface Settings extends Service { | |
| 37 | ||
| 38 | /** | |
| 39 | * Returns a setting property or its default value. | |
| 40 | * | |
| 41 | * @param property The property key name to obtain its value. | |
| 42 | * @param defaultValue The default value to return iff the property cannot be | |
| 43 | * found. | |
| 44 | * @return The property value for the given property key. | |
| 45 | */ | |
| 46 | String getSetting( String property, String defaultValue ); | |
| 47 | ||
| 48 | /** | |
| 49 | * Returns a setting property or its default value. | |
| 50 | * | |
| 51 | * @param property The property key name to obtain its value. | |
| 52 | * @param defaultValue The default value to return iff the property cannot be | |
| 53 | * found. | |
| 54 | * @return The property value for the given property key. | |
| 55 | */ | |
| 56 | int getSetting( String property, int defaultValue ); | |
| 57 | ||
| 58 | /** | |
| 59 | * Returns a list of property names that begin with the given prefix. The | |
| 60 | * prefix is included in any matching results. This will return keys that | |
| 61 | * either match the prefix or start with the prefix followed by a dot ('.'). | |
| 62 | * For example, a prefix value of <code>the.property.name</code> will likely | |
| 63 | * return the expected results, but <code>the.property.name.</code> (note the | |
| 64 | * extraneous period) will probably not. | |
| 65 | * | |
| 66 | * @param prefix The prefix to compare against each property name. | |
| 67 | * @return The list of property names that have the given prefix. | |
| 68 | */ | |
| 69 | Iterator<String> getKeys( final String prefix ); | |
| 70 | ||
| 71 | /** | |
| 72 | * Convert the generic list of property objects into strings. | |
| 73 | * | |
| 74 | * @param property The property value to coerce. | |
| 75 | * @param defaults The defaults values to use should the property be unset. | |
| 76 | * @return The list of properties coerced from objects to strings. | |
| 77 | */ | |
| 78 | List<String> getStringSettingList( String property, List<String> defaults ); | |
| 79 | ||
| 80 | /** | |
| 81 | * Converts the generic list of property objects into strings. | |
| 82 | * | |
| 83 | * @param property The property value to coerce. | |
| 84 | * @return The list of properties coerced from objects to strings. | |
| 85 | */ | |
| 86 | List<String> getStringSettingList( String property ); | |
| 87 | } | |
| 1 | 88 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service; | |
| 29 | ||
| 30 | import java.io.IOException; | |
| 31 | import java.nio.file.Path; | |
| 32 | import java.util.Observer; | |
| 33 | ||
| 34 | /** | |
| 35 | * Listens for changes to file system files and directories. | |
| 36 | */ | |
| 37 | public interface Snitch extends Service, Runnable { | |
| 38 | ||
| 39 | /** | |
| 40 | * Adds an observer to the set of observers for this object, provided that it | |
| 41 | * is not the same as some observer already in the set. The order in which | |
| 42 | * notifications will be delivered to multiple observers is not specified. | |
| 43 | * | |
| 44 | * @param o The object to receive changed events for when monitored files | |
| 45 | * are changed. | |
| 46 | */ | |
| 47 | void addObserver( Observer o ); | |
| 48 | ||
| 49 | /** | |
| 50 | * Listens for changes to the path. If the path specifies a file, then only | |
| 51 | * notifications pertaining to that file are sent. Otherwise, change events | |
| 52 | * for the directory that contains the file are sent. This method must allow | |
| 53 | * for multiple calls to the same file without incurring additional listeners | |
| 54 | * or events. | |
| 55 | * | |
| 56 | * @param file Send notifications when this file changes, can be null. | |
| 57 | * @throws IOException Couldn't create a watcher for the given file. | |
| 58 | */ | |
| 59 | void listen( Path file ) throws IOException; | |
| 60 | ||
| 61 | /** | |
| 62 | * Removes the given file from the notifications list. | |
| 63 | * | |
| 64 | * @param file The file to stop monitoring for any changes, can be null. | |
| 65 | */ | |
| 66 | void ignore( Path file ); | |
| 67 | ||
| 68 | /** | |
| 69 | * Stop listening for events. | |
| 70 | */ | |
| 71 | void stop(); | |
| 72 | } | |
| 1 | 73 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents a message that contains a title and content. | |
| 32 | */ | |
| 33 | public interface Notification { | |
| 34 | ||
| 35 | /** | |
| 36 | * Alert title. | |
| 37 | * | |
| 38 | * @return A non-null string to use as alert message title. | |
| 39 | */ | |
| 40 | String getTitle(); | |
| 41 | ||
| 42 | /** | |
| 43 | * Alert message content. | |
| 44 | * | |
| 45 | * @return A non-null string that contains information for the user. | |
| 46 | */ | |
| 47 | String getContent(); | |
| 48 | } | |
| 1 | 49 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events; | |
| 29 | ||
| 30 | import javafx.scene.control.Alert; | |
| 31 | import javafx.scene.control.ButtonType; | |
| 32 | import javafx.stage.Window; | |
| 33 | ||
| 34 | /** | |
| 35 | * Provides the application with a uniform way to notify the user of events. | |
| 36 | */ | |
| 37 | public interface Notifier { | |
| 38 | ||
| 39 | ButtonType YES = ButtonType.YES; | |
| 40 | ButtonType NO = ButtonType.NO; | |
| 41 | ButtonType CANCEL = ButtonType.CANCEL; | |
| 42 | ||
| 43 | /** | |
| 44 | * Constructs a default alert message text for a modal alert dialog. | |
| 45 | * | |
| 46 | * @param title The dialog box message title. | |
| 47 | * @param message The dialog box message content (needs formatting). | |
| 48 | * @param args The arguments to the message content that must be formatted. | |
| 49 | * @return The message suitable for building a modal alert dialog. | |
| 50 | */ | |
| 51 | Notification createNotification( | |
| 52 | String title, | |
| 53 | String message, | |
| 54 | Object... args ); | |
| 55 | ||
| 56 | /** | |
| 57 | * Creates an alert of alert type error with a message showing the cause of | |
| 58 | * the error. | |
| 59 | * | |
| 60 | * @param parent Dialog box owner (for modal purposes). | |
| 61 | * @param message The error message, title, and possibly more details. | |
| 62 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 63 | */ | |
| 64 | Alert createError( Window parent, Notification message ); | |
| 65 | ||
| 66 | /** | |
| 67 | * Creates an alert of alert type confirmation with Yes/No/Cancel buttons. | |
| 68 | * | |
| 69 | * @param parent Dialog box owner (for modal purposes). | |
| 70 | * @param message The message, title, and possibly more details. | |
| 71 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 72 | */ | |
| 73 | Alert createConfirmation( Window parent, Notification message ); | |
| 74 | } | |
| 1 | 75 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events.impl; | |
| 29 | ||
| 30 | import javafx.scene.Node; | |
| 31 | import javafx.scene.control.ButtonBar; | |
| 32 | import javafx.scene.control.DialogPane; | |
| 33 | ||
| 34 | import static com.scrivenvar.Constants.SETTINGS; | |
| 35 | import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS; | |
| 36 | ||
| 37 | /** | |
| 38 | * Ensures a consistent button order for alert dialogs across platforms (because | |
| 39 | * the default button order on Linux defies all logic). | |
| 40 | */ | |
| 41 | public class ButtonOrderPane extends DialogPane { | |
| 42 | ||
| 43 | @Override | |
| 44 | protected Node createButtonBar() { | |
| 45 | final var node = (ButtonBar) super.createButtonBar(); | |
| 46 | node.setButtonOrder( getButtonOrder() ); | |
| 47 | return node; | |
| 48 | } | |
| 49 | ||
| 50 | private String getButtonOrder() { | |
| 51 | return getSetting( "dialog.alert.button.order.windows", | |
| 52 | BUTTON_ORDER_WINDOWS ); | |
| 53 | } | |
| 54 | ||
| 55 | @SuppressWarnings("SameParameterValue") | |
| 56 | private String getSetting( final String key, final String defaultValue ) { | |
| 57 | return SETTINGS.getSetting( key, defaultValue ); | |
| 58 | } | |
| 59 | } | |
| 1 | 60 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.events.Notification; | |
| 31 | ||
| 32 | import java.text.MessageFormat; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for alerting the user to prominent information. | |
| 36 | */ | |
| 37 | public class DefaultNotification implements Notification { | |
| 38 | ||
| 39 | private final String title; | |
| 40 | private final String content; | |
| 41 | ||
| 42 | /** | |
| 43 | * Constructs default message text for a notification. | |
| 44 | * | |
| 45 | * @param title The message title. | |
| 46 | * @param message The message content (needs formatting). | |
| 47 | * @param args The arguments to the message content that must be formatted. | |
| 48 | */ | |
| 49 | public DefaultNotification( | |
| 50 | final String title, | |
| 51 | final String message, | |
| 52 | final Object... args ) { | |
| 53 | this.title = title; | |
| 54 | this.content = MessageFormat.format( message, args ); | |
| 55 | } | |
| 56 | ||
| 57 | @Override | |
| 58 | public String getTitle() { | |
| 59 | return this.title; | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public String getContent() { | |
| 64 | return this.content; | |
| 65 | } | |
| 66 | } | |
| 1 | 67 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.events.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.events.Notification; | |
| 31 | import com.scrivenvar.service.events.Notifier; | |
| 32 | import javafx.scene.control.Alert; | |
| 33 | import javafx.scene.control.Alert.AlertType; | |
| 34 | import javafx.stage.Window; | |
| 35 | ||
| 36 | import static javafx.scene.control.Alert.AlertType.CONFIRMATION; | |
| 37 | import static javafx.scene.control.Alert.AlertType.ERROR; | |
| 38 | ||
| 39 | /** | |
| 40 | * Provides the ability to notify the user of events that need attention, | |
| 41 | * such as prompting the user to confirm closing when there are unsaved changes. | |
| 42 | */ | |
| 43 | public final class DefaultNotifier implements Notifier { | |
| 44 | ||
| 45 | /** | |
| 46 | * Contains all the information that the user needs to know about a problem. | |
| 47 | * | |
| 48 | * @param title The context for the message. | |
| 49 | * @param message The message content (formatted with the given args). | |
| 50 | * @param args Parameters for the message content. | |
| 51 | * @return A notification instance, never null. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public Notification createNotification( | |
| 55 | final String title, | |
| 56 | final String message, | |
| 57 | final Object... args ) { | |
| 58 | return new DefaultNotification( title, message, args ); | |
| 59 | } | |
| 60 | ||
| 61 | private Alert createAlertDialog( | |
| 62 | final Window parent, | |
| 63 | final AlertType alertType, | |
| 64 | final Notification message ) { | |
| 65 | ||
| 66 | final Alert alert = new Alert( alertType ); | |
| 67 | ||
| 68 | alert.setDialogPane( new ButtonOrderPane() ); | |
| 69 | alert.setTitle( message.getTitle() ); | |
| 70 | alert.setHeaderText( null ); | |
| 71 | alert.setContentText( message.getContent() ); | |
| 72 | alert.initOwner( parent ); | |
| 73 | ||
| 74 | return alert; | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public Alert createConfirmation( final Window parent, | |
| 79 | final Notification message ) { | |
| 80 | final Alert alert = createAlertDialog( parent, CONFIRMATION, message ); | |
| 81 | ||
| 82 | alert.getButtonTypes().setAll( YES, NO, CANCEL ); | |
| 83 | ||
| 84 | return alert; | |
| 85 | } | |
| 86 | ||
| 87 | @Override | |
| 88 | public Alert createError( final Window parent, final Notification message ) { | |
| 89 | return createAlertDialog( parent, ERROR, message ); | |
| 90 | } | |
| 91 | } | |
| 1 | 92 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.service.impl; | |
| 28 | ||
| 29 | import com.scrivenvar.preferences.UserPreferences; | |
| 30 | import com.scrivenvar.service.Options; | |
| 31 | ||
| 32 | import java.util.prefs.BackingStoreException; | |
| 33 | import java.util.prefs.Preferences; | |
| 34 | ||
| 35 | import static com.scrivenvar.Constants.PREFS_ROOT; | |
| 36 | import static com.scrivenvar.Constants.PREFS_STATE; | |
| 37 | import static java.util.prefs.Preferences.userRoot; | |
| 38 | ||
| 39 | /** | |
| 40 | * Persistent options user can change at runtime. | |
| 41 | */ | |
| 42 | public class DefaultOptions implements Options { | |
| 43 | private final UserPreferences mPreferences = new UserPreferences(); | |
| 44 | ||
| 45 | public DefaultOptions() { | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * This will throw IllegalArgumentException if the value exceeds the maximum | |
| 50 | * preferences value length. | |
| 51 | * | |
| 52 | * @param key The name of the key to associate with the value. | |
| 53 | * @param value The value to persist. | |
| 54 | * @throws BackingStoreException New value not persisted. | |
| 55 | */ | |
| 56 | @Override | |
| 57 | public void put( final String key, final String value ) | |
| 58 | throws BackingStoreException { | |
| 59 | getState().put( key, value ); | |
| 60 | getState().flush(); | |
| 61 | } | |
| 62 | ||
| 63 | @Override | |
| 64 | public String get( final String key, final String value ) { | |
| 65 | return getState().get( key, value ); | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public String get( final String key ) { | |
| 70 | return get( key, "" ); | |
| 71 | } | |
| 72 | ||
| 73 | private Preferences getRootPreferences() { | |
| 74 | return userRoot().node( PREFS_ROOT ); | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public Preferences getState() { | |
| 79 | return getRootPreferences().node( PREFS_STATE ); | |
| 80 | } | |
| 81 | ||
| 82 | @Override | |
| 83 | public UserPreferences getUserPreferences() { | |
| 84 | return mPreferences; | |
| 85 | } | |
| 86 | } | |
| 1 | 87 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Settings; | |
| 31 | import org.apache.commons.configuration2.PropertiesConfiguration; | |
| 32 | import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; | |
| 33 | import org.apache.commons.configuration2.convert.ListDelimiterHandler; | |
| 34 | import org.apache.commons.configuration2.ex.ConfigurationException; | |
| 35 | ||
| 36 | import java.io.IOException; | |
| 37 | import java.io.InputStreamReader; | |
| 38 | import java.io.Reader; | |
| 39 | import java.net.URL; | |
| 40 | import java.nio.charset.Charset; | |
| 41 | import java.util.Iterator; | |
| 42 | import java.util.List; | |
| 43 | ||
| 44 | import static com.scrivenvar.Constants.SETTINGS_NAME; | |
| 45 | ||
| 46 | /** | |
| 47 | * Responsible for loading settings that help avoid hard-coded assumptions. | |
| 48 | */ | |
| 49 | public class DefaultSettings implements Settings { | |
| 50 | ||
| 51 | private static final char VALUE_SEPARATOR = ','; | |
| 52 | ||
| 53 | private PropertiesConfiguration properties; | |
| 54 | ||
| 55 | public DefaultSettings() throws ConfigurationException { | |
| 56 | setProperties( createProperties() ); | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * Returns the value of a string property. | |
| 61 | * | |
| 62 | * @param property The property key. | |
| 63 | * @param defaultValue The value to return if no property key has been set. | |
| 64 | * @return The property key value, or defaultValue when no key found. | |
| 65 | */ | |
| 66 | @Override | |
| 67 | public String getSetting( final String property, final String defaultValue ) { | |
| 68 | return getSettings().getString( property, defaultValue ); | |
| 69 | } | |
| 70 | ||
| 71 | /** | |
| 72 | * Returns the value of a string property. | |
| 73 | * | |
| 74 | * @param property The property key. | |
| 75 | * @param defaultValue The value to return if no property key has been set. | |
| 76 | * @return The property key value, or defaultValue when no key found. | |
| 77 | */ | |
| 78 | @Override | |
| 79 | public int getSetting( final String property, final int defaultValue ) { | |
| 80 | return getSettings().getInt( property, defaultValue ); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Convert the generic list of property objects into strings. | |
| 85 | * | |
| 86 | * @param property The property value to coerce. | |
| 87 | * @param defaults The defaults values to use should the property be unset. | |
| 88 | * @return The list of properties coerced from objects to strings. | |
| 89 | */ | |
| 90 | @Override | |
| 91 | public List<String> getStringSettingList( | |
| 92 | final String property, final List<String> defaults ) { | |
| 93 | return getSettings().getList( String.class, property, defaults ); | |
| 94 | } | |
| 95 | ||
| 96 | /** | |
| 97 | * Convert a list of property objects into strings, with no default value. | |
| 98 | * | |
| 99 | * @param property The property value to coerce. | |
| 100 | * @return The list of properties coerced from objects to strings. | |
| 101 | */ | |
| 102 | @Override | |
| 103 | public List<String> getStringSettingList( final String property ) { | |
| 104 | return getStringSettingList( property, null ); | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Returns a list of property names that begin with the given prefix. | |
| 109 | * | |
| 110 | * @param prefix The prefix to compare against each property name. | |
| 111 | * @return The list of property names that have the given prefix. | |
| 112 | */ | |
| 113 | @Override | |
| 114 | public Iterator<String> getKeys( final String prefix ) { | |
| 115 | return getSettings().getKeys( prefix ); | |
| 116 | } | |
| 117 | ||
| 118 | private PropertiesConfiguration createProperties() | |
| 119 | throws ConfigurationException { | |
| 120 | ||
| 121 | final URL url = getPropertySource(); | |
| 122 | final PropertiesConfiguration configuration = new PropertiesConfiguration(); | |
| 123 | ||
| 124 | if( url != null ) { | |
| 125 | try( final Reader r = new InputStreamReader( url.openStream(), | |
| 126 | getDefaultEncoding() ) ) { | |
| 127 | configuration.setListDelimiterHandler( createListDelimiterHandler() ); | |
| 128 | configuration.read( r ); | |
| 129 | ||
| 130 | } catch( final IOException ex ) { | |
| 131 | throw new RuntimeException( new ConfigurationException( ex ) ); | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | return configuration; | |
| 136 | } | |
| 137 | ||
| 138 | protected Charset getDefaultEncoding() { | |
| 139 | return Charset.defaultCharset(); | |
| 140 | } | |
| 141 | ||
| 142 | protected ListDelimiterHandler createListDelimiterHandler() { | |
| 143 | return new DefaultListDelimiterHandler( VALUE_SEPARATOR ); | |
| 144 | } | |
| 145 | ||
| 146 | private URL getPropertySource() { | |
| 147 | return DefaultSettings.class.getResource( getSettingsFilename() ); | |
| 148 | } | |
| 149 | ||
| 150 | private String getSettingsFilename() { | |
| 151 | return SETTINGS_NAME; | |
| 152 | } | |
| 153 | ||
| 154 | private void setProperties( final PropertiesConfiguration configuration ) { | |
| 155 | this.properties = configuration; | |
| 156 | } | |
| 157 | ||
| 158 | private PropertiesConfiguration getSettings() { | |
| 159 | return this.properties; | |
| 160 | } | |
| 161 | } | |
| 1 | 162 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.service.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.service.Snitch; | |
| 31 | ||
| 32 | import java.io.IOException; | |
| 33 | import java.nio.file.*; | |
| 34 | import java.util.Collections; | |
| 35 | import java.util.Map; | |
| 36 | import java.util.Observable; | |
| 37 | import java.util.Set; | |
| 38 | import java.util.concurrent.ConcurrentHashMap; | |
| 39 | ||
| 40 | import static com.scrivenvar.Constants.APP_WATCHDOG_TIMEOUT; | |
| 41 | import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; | |
| 42 | ||
| 43 | /** | |
| 44 | * Listens for file changes. Other classes can register paths to be monitored | |
| 45 | * and listen for changes to those paths. | |
| 46 | */ | |
| 47 | public class DefaultSnitch extends Observable implements Snitch { | |
| 48 | ||
| 49 | /** | |
| 50 | * Service for listening to directories for modifications. | |
| 51 | */ | |
| 52 | private WatchService watchService; | |
| 53 | ||
| 54 | /** | |
| 55 | * Directories being monitored for changes. | |
| 56 | */ | |
| 57 | private Map<WatchKey, Path> keys; | |
| 58 | ||
| 59 | /** | |
| 60 | * Files that will kick off notification events if modified. | |
| 61 | */ | |
| 62 | private Set<Path> eavesdropped; | |
| 63 | ||
| 64 | /** | |
| 65 | * Set to true when running; set to false to stop listening. | |
| 66 | */ | |
| 67 | private volatile boolean listening; | |
| 68 | ||
| 69 | public DefaultSnitch() { | |
| 70 | } | |
| 71 | ||
| 72 | @Override | |
| 73 | public void stop() { | |
| 74 | setListening( false ); | |
| 75 | } | |
| 76 | ||
| 77 | /** | |
| 78 | * Adds a listener to the list of files to watch for changes. If the file is | |
| 79 | * already in the monitored list, this will return immediately. | |
| 80 | * | |
| 81 | * @param file Path to a file to watch for changes. | |
| 82 | * @throws IOException The file could not be monitored. | |
| 83 | */ | |
| 84 | @Override | |
| 85 | public void listen( final Path file ) throws IOException { | |
| 86 | if( file != null && getEavesdropped().add( file ) ) { | |
| 87 | final Path dir = toDirectory( file ); | |
| 88 | final WatchKey key = dir.register( getWatchService(), ENTRY_MODIFY ); | |
| 89 | ||
| 90 | getWatchMap().put( key, dir ); | |
| 91 | } | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Returns the given path to a file (or directory) as a directory. If the | |
| 96 | * given path is already a directory, it is returned. Otherwise, this returns | |
| 97 | * the directory that contains the file. This will fail if the file is stored | |
| 98 | * in the root folder. | |
| 99 | * | |
| 100 | * @param path The file to return as a directory, which should always be the | |
| 101 | * case. | |
| 102 | * @return The given path as a directory, if a file, otherwise the path | |
| 103 | * itself. | |
| 104 | */ | |
| 105 | private Path toDirectory( final Path path ) { | |
| 106 | return Files.isDirectory( path ) | |
| 107 | ? path | |
| 108 | : path.toFile().getParentFile().toPath(); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Stop listening to the given file for change events. This fails silently. | |
| 113 | * | |
| 114 | * @param file The file to no longer monitor for changes. | |
| 115 | */ | |
| 116 | @Override | |
| 117 | public void ignore( final Path file ) { | |
| 118 | if( file != null ) { | |
| 119 | final Path directory = toDirectory( file ); | |
| 120 | ||
| 121 | // Remove all occurrences (there should be only one). | |
| 122 | getWatchMap().values().removeAll( Collections.singleton( directory ) ); | |
| 123 | ||
| 124 | // Remove all occurrences (there can be only one). | |
| 125 | getEavesdropped().remove( file ); | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | /** | |
| 130 | * Loops until stop is called, or the application is terminated. | |
| 131 | */ | |
| 132 | @Override | |
| 133 | @SuppressWarnings("BusyWait") | |
| 134 | public void run() { | |
| 135 | setListening( true ); | |
| 136 | ||
| 137 | while( isListening() ) { | |
| 138 | try { | |
| 139 | final WatchKey key = getWatchService().take(); | |
| 140 | final Path path = get( key ); | |
| 141 | ||
| 142 | // Prevent receiving two separate ENTRY_MODIFY events: file modified | |
| 143 | // and timestamp updated. Instead, receive one ENTRY_MODIFY event | |
| 144 | // with two counts. | |
| 145 | Thread.sleep( APP_WATCHDOG_TIMEOUT ); | |
| 146 | ||
| 147 | for( final WatchEvent<?> event : key.pollEvents() ) { | |
| 148 | final Path changed = path.resolve( (Path) event.context() ); | |
| 149 | ||
| 150 | if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) { | |
| 151 | setChanged(); | |
| 152 | notifyObservers( changed ); | |
| 153 | } | |
| 154 | } | |
| 155 | ||
| 156 | if( !key.reset() ) { | |
| 157 | ignore( path ); | |
| 158 | } | |
| 159 | } catch( final IOException | InterruptedException ex ) { | |
| 160 | // Stop eavesdropping. | |
| 161 | setListening( false ); | |
| 162 | } | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | /** | |
| 167 | * Returns true if the list of files being listened to for changes contains | |
| 168 | * the given file. | |
| 169 | * | |
| 170 | * @param file Path to a system file. | |
| 171 | * @return true The given file is being monitored for changes. | |
| 172 | */ | |
| 173 | private boolean isListening( final Path file ) { | |
| 174 | return getEavesdropped().contains( file ); | |
| 175 | } | |
| 176 | ||
| 177 | /** | |
| 178 | * Returns a path for a given watch key. | |
| 179 | * | |
| 180 | * @param key The key to lookup its corresponding path. | |
| 181 | * @return The path for the given key. | |
| 182 | */ | |
| 183 | private Path get( final WatchKey key ) { | |
| 184 | return getWatchMap().get( key ); | |
| 185 | } | |
| 186 | ||
| 187 | private synchronized Map<WatchKey, Path> getWatchMap() { | |
| 188 | if( this.keys == null ) { | |
| 189 | this.keys = createWatchKeys(); | |
| 190 | } | |
| 191 | ||
| 192 | return this.keys; | |
| 193 | } | |
| 194 | ||
| 195 | protected Map<WatchKey, Path> createWatchKeys() { | |
| 196 | return new ConcurrentHashMap<>(); | |
| 197 | } | |
| 198 | ||
| 199 | /** | |
| 200 | * Returns a list of files that, when changed, will kick off a notification. | |
| 201 | * | |
| 202 | * @return A non-null, possibly empty, list of files. | |
| 203 | */ | |
| 204 | private synchronized Set<Path> getEavesdropped() { | |
| 205 | if( this.eavesdropped == null ) { | |
| 206 | this.eavesdropped = createEavesdropped(); | |
| 207 | } | |
| 208 | ||
| 209 | return this.eavesdropped; | |
| 210 | } | |
| 211 | ||
| 212 | protected Set<Path> createEavesdropped() { | |
| 213 | return ConcurrentHashMap.newKeySet(); | |
| 214 | } | |
| 215 | ||
| 216 | /** | |
| 217 | * The existing watch service, or a new instance if null. | |
| 218 | * | |
| 219 | * @return A valid WatchService instance, never null. | |
| 220 | * @throws IOException Could not create a new watch service. | |
| 221 | */ | |
| 222 | private synchronized WatchService getWatchService() throws IOException { | |
| 223 | if( this.watchService == null ) { | |
| 224 | this.watchService = createWatchService(); | |
| 225 | } | |
| 226 | ||
| 227 | return this.watchService; | |
| 228 | } | |
| 229 | ||
| 230 | protected WatchService createWatchService() throws IOException { | |
| 231 | final FileSystem fileSystem = FileSystems.getDefault(); | |
| 232 | return fileSystem.newWatchService(); | |
| 233 | } | |
| 234 | ||
| 235 | /** | |
| 236 | * Answers whether the loop should continue executing. | |
| 237 | * | |
| 238 | * @return true The internal listening loop should continue listening for file | |
| 239 | * modification events. | |
| 240 | */ | |
| 241 | protected boolean isListening() { | |
| 242 | return this.listening; | |
| 243 | } | |
| 244 | ||
| 245 | /** | |
| 246 | * Requests the snitch to stop eavesdropping on file changes. | |
| 247 | * | |
| 248 | * @param listening Use true to indicate the service should stop running. | |
| 249 | */ | |
| 250 | private void setListening( final boolean listening ) { | |
| 251 | this.listening = listening; | |
| 252 | } | |
| 253 | } | |
| 1 | 254 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.sigils; | |
| 29 | ||
| 30 | import static com.scrivenvar.sigils.YamlSigilOperator.KEY_SEPARATOR_DEF; | |
| 31 | ||
| 32 | /** | |
| 33 | * Brackets variable names between {@link #PREFIX} and {@link #SUFFIX} sigils. | |
| 34 | */ | |
| 35 | public class RSigilOperator extends SigilOperator { | |
| 36 | public static final char KEY_SEPARATOR_R = '$'; | |
| 37 | ||
| 38 | public static final String PREFIX = "`r#"; | |
| 39 | public static final char SUFFIX = '`'; | |
| 40 | ||
| 41 | private final String mDelimiterBegan = | |
| 42 | getUserPreferences().getRDelimiterBegan(); | |
| 43 | private final String mDelimiterEnded = | |
| 44 | getUserPreferences().getRDelimiterEnded(); | |
| 45 | ||
| 46 | /** | |
| 47 | * Returns the given string R-escaping backticks prepended and appended. This | |
| 48 | * is not null safe. Do not pass null into this method. | |
| 49 | * | |
| 50 | * @param key The string to adorn with R token delimiters. | |
| 51 | * @return "`r#" + delimiterBegan + variableName+ delimiterEnded + "`". | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public String apply( final String key ) { | |
| 55 | assert key != null; | |
| 56 | ||
| 57 | return PREFIX | |
| 58 | + mDelimiterBegan | |
| 59 | + entoken( key ) | |
| 60 | + mDelimiterEnded | |
| 61 | + SUFFIX; | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Transforms a definition key (bracketed by token delimiters) into the | |
| 66 | * expected format for an R variable key name. | |
| 67 | * | |
| 68 | * @param key The variable name to transform, can be empty but not null. | |
| 69 | * @return The transformed variable name. | |
| 70 | */ | |
| 71 | public static String entoken( final String key ) { | |
| 72 | return "v$" + | |
| 73 | YamlSigilOperator.detoken( key ) | |
| 74 | .replace( KEY_SEPARATOR_DEF, KEY_SEPARATOR_R ); | |
| 75 | } | |
| 76 | } | |
| 1 | 77 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.sigils; | |
| 29 | ||
| 30 | import com.scrivenvar.Services; | |
| 31 | import com.scrivenvar.preferences.UserPreferences; | |
| 32 | import com.scrivenvar.service.Options; | |
| 33 | ||
| 34 | import java.util.function.UnaryOperator; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for updating definition keys to use a machine-readable format | |
| 38 | * corresponding to the type of file being edited. This changes a definition | |
| 39 | * key name based on some criteria determined by the factory that creates | |
| 40 | * implementations of this interface. | |
| 41 | */ | |
| 42 | public abstract class SigilOperator implements UnaryOperator<String> { | |
| 43 | private static final Options sOptions = Services.load( Options.class ); | |
| 44 | ||
| 45 | protected static UserPreferences getUserPreferences() { | |
| 46 | return sOptions.getUserPreferences(); | |
| 47 | } | |
| 48 | } | |
| 1 | 49 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.sigils; | |
| 29 | ||
| 30 | import java.util.regex.Pattern; | |
| 31 | ||
| 32 | import static java.lang.String.format; | |
| 33 | import static java.util.regex.Pattern.compile; | |
| 34 | import static java.util.regex.Pattern.quote; | |
| 35 | ||
| 36 | /** | |
| 37 | * Brackets definition keys with token delimiters. | |
| 38 | */ | |
| 39 | public class YamlSigilOperator extends SigilOperator { | |
| 40 | public static final char KEY_SEPARATOR_DEF = '.'; | |
| 41 | ||
| 42 | private static final String mDelimiterBegan = | |
| 43 | getUserPreferences().getDefDelimiterBegan(); | |
| 44 | private static final String mDelimiterEnded = | |
| 45 | getUserPreferences().getDefDelimiterEnded(); | |
| 46 | ||
| 47 | /** | |
| 48 | * Non-greedy match of key names delimited by definition tokens. | |
| 49 | */ | |
| 50 | private static final String REGEX = | |
| 51 | format( "(%s.*?%s)", quote( mDelimiterBegan ), quote( mDelimiterEnded ) ); | |
| 52 | ||
| 53 | /** | |
| 54 | * Compiled regular expression for matching delimited references. | |
| 55 | */ | |
| 56 | public static final Pattern REGEX_PATTERN = compile( REGEX ); | |
| 57 | ||
| 58 | /** | |
| 59 | * Returns the given {@link String} verbatim because variables in YAML | |
| 60 | * documents and plain Markdown documents already have the appropriate | |
| 61 | * tokenizable syntax wrapped around the text. | |
| 62 | * | |
| 63 | * @param key Returned verbatim. | |
| 64 | */ | |
| 65 | @Override | |
| 66 | public String apply( final String key ) { | |
| 67 | return key; | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Adds delimiters to the given key. | |
| 72 | * | |
| 73 | * @param key The key to adorn with start and stop definition tokens. | |
| 74 | * @return The given key bracketed by definition token symbols. | |
| 75 | */ | |
| 76 | public static String entoken( final String key ) { | |
| 77 | assert key != null; | |
| 78 | return mDelimiterBegan + key + mDelimiterEnded; | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Removes start and stop definition key delimiters from the given key. This | |
| 83 | * method does not check for delimiters, only that there are sufficient | |
| 84 | * characters to remove from either end of the given key. | |
| 85 | * | |
| 86 | * @param key The key adorned with start and stop definition tokens. | |
| 87 | * @return The given key with the delimiters removed. | |
| 88 | */ | |
| 89 | public static String detoken( final String key ) { | |
| 90 | final int beganLen = mDelimiterBegan.length(); | |
| 91 | final int endedLen = mDelimiterEnded.length(); | |
| 92 | ||
| 93 | return key.length() > beganLen + endedLen | |
| 94 | ? key.substring( beganLen, key.length() - endedLen ) | |
| 95 | : key; | |
| 96 | } | |
| 97 | } | |
| 1 | 98 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.spelling.api; | |
| 29 | ||
| 30 | import java.util.function.BiConsumer; | |
| 31 | ||
| 32 | /** | |
| 33 | * Represents an operation that accepts two input arguments and returns no | |
| 34 | * result. Unlike most other functional interfaces, this class is expected to | |
| 35 | * operate via side-effects. | |
| 36 | * <p> | |
| 37 | * This is used instead of a {@link BiConsumer} to avoid autoboxing. | |
| 38 | * </p> | |
| 39 | */ | |
| 40 | @FunctionalInterface | |
| 41 | public interface SpellCheckListener { | |
| 42 | ||
| 43 | /** | |
| 44 | * Performs an operation on the given arguments. | |
| 45 | * | |
| 46 | * @param text The text associated with a beginning and ending offset. | |
| 47 | * @param beganOffset A starting offset, used as an index into a string. | |
| 48 | * @param endedOffset An ending offset, which should equal text.length() + | |
| 49 | * beganOffset. | |
| 50 | */ | |
| 51 | void accept( String text, int beganOffset, int endedOffset ); | |
| 52 | } | |
| 1 | 53 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.spelling.api; | |
| 29 | ||
| 30 | import java.util.List; | |
| 31 | ||
| 32 | /** | |
| 33 | * Defines the responsibilities for a spell checking API. The intention is | |
| 34 | * to allow different spell checking implementations to be used by the | |
| 35 | * application, such as SymSpell and LinSpell. | |
| 36 | */ | |
| 37 | public interface SpellChecker { | |
| 38 | ||
| 39 | /** | |
| 40 | * Answers whether the given lexeme, in whole, is found in the lexicon. The | |
| 41 | * lexicon lookup is performed case-insensitively. This method should be | |
| 42 | * used instead of {@link #suggestions(String, int)} for performance reasons. | |
| 43 | * | |
| 44 | * @param lexeme The word to check for correctness. | |
| 45 | * @return {@code true} if the lexeme is in the lexicon. | |
| 46 | */ | |
| 47 | boolean inLexicon( String lexeme ); | |
| 48 | ||
| 49 | /** | |
| 50 | * Gets a list of spelling corrections for the given lexeme. | |
| 51 | * | |
| 52 | * @param lexeme A word to check for correctness that's not in the lexicon. | |
| 53 | * @param count The maximum number of alternatives to return. | |
| 54 | * @return A list of words in the lexicon that are similar to the given | |
| 55 | * lexeme. | |
| 56 | */ | |
| 57 | List<String> suggestions( String lexeme, int count ); | |
| 58 | ||
| 59 | /** | |
| 60 | * Iterates over the given text, emitting starting and ending offsets into | |
| 61 | * the text for every word that is missing from the lexicon. | |
| 62 | * | |
| 63 | * @param text The text to check for words missing from the lexicon. | |
| 64 | * @param consumer Every missing word emits a message with the starting | |
| 65 | * and ending offset into the text where said word is found. | |
| 66 | */ | |
| 67 | void proofread( String text, SpellCheckListener consumer ); | |
| 68 | } | |
| 1 | 69 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.spelling.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.spelling.api.SpellCheckListener; | |
| 31 | import com.scrivenvar.spelling.api.SpellChecker; | |
| 32 | ||
| 33 | import java.util.List; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for spell checking in the event that a real spell checking | |
| 37 | * implementation cannot be created (for any reason). Does not perform any | |
| 38 | * spell checking and indicates that any given lexeme is in the lexicon. | |
| 39 | */ | |
| 40 | public class PermissiveSpeller implements SpellChecker { | |
| 41 | /** | |
| 42 | * Returns {@code true}, ignoring the given word. | |
| 43 | * | |
| 44 | * @param ignored Unused. | |
| 45 | * @return {@code true} | |
| 46 | */ | |
| 47 | @Override | |
| 48 | public boolean inLexicon( final String ignored ) { | |
| 49 | return true; | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Returns an array with the given lexeme. | |
| 54 | * | |
| 55 | * @param lexeme The word to return. | |
| 56 | * @param ignored Unused. | |
| 57 | * @return A suggestion list containing the given lexeme. | |
| 58 | */ | |
| 59 | @Override | |
| 60 | public List<String> suggestions( final String lexeme, final int ignored ) { | |
| 61 | return List.of( lexeme ); | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Performs no action. | |
| 66 | * | |
| 67 | * @param text Unused. | |
| 68 | * @param ignored Uncalled. | |
| 69 | */ | |
| 70 | @Override | |
| 71 | public void proofread( | |
| 72 | final String text, final SpellCheckListener ignored ) { | |
| 73 | } | |
| 74 | } | |
| 1 | 75 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.spelling.impl; | |
| 29 | ||
| 30 | import com.scrivenvar.spelling.api.SpellCheckListener; | |
| 31 | import com.scrivenvar.spelling.api.SpellChecker; | |
| 32 | import io.gitlab.rxp90.jsymspell.SuggestItem; | |
| 33 | import io.gitlab.rxp90.jsymspell.SymSpell; | |
| 34 | import io.gitlab.rxp90.jsymspell.SymSpellBuilder; | |
| 35 | ||
| 36 | import java.text.BreakIterator; | |
| 37 | import java.util.ArrayList; | |
| 38 | import java.util.Collection; | |
| 39 | import java.util.List; | |
| 40 | ||
| 41 | import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity; | |
| 42 | import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity.ALL; | |
| 43 | import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity.CLOSEST; | |
| 44 | import static java.lang.Character.isLetter; | |
| 45 | ||
| 46 | /** | |
| 47 | * Responsible for spell checking using {@link SymSpell}. | |
| 48 | */ | |
| 49 | public class SymSpellSpeller implements SpellChecker { | |
| 50 | private final BreakIterator mBreakIterator = BreakIterator.getWordInstance(); | |
| 51 | ||
| 52 | private final SymSpell mSymSpell; | |
| 53 | ||
| 54 | /** | |
| 55 | * Creates a new lexicon for the given collection of lexemes. | |
| 56 | * | |
| 57 | * @param lexiconWords The words in the lexicon to add for spell checking, | |
| 58 | * must not be empty. | |
| 59 | * @return An instance of {@link SpellChecker} that can check if a word | |
| 60 | * is correct and suggest alternatives. | |
| 61 | */ | |
| 62 | public static SpellChecker forLexicon( | |
| 63 | final Collection<String> lexiconWords ) { | |
| 64 | assert lexiconWords != null && !lexiconWords.isEmpty(); | |
| 65 | ||
| 66 | final SymSpellBuilder builder = new SymSpellBuilder() | |
| 67 | .setLexiconWords( lexiconWords ); | |
| 68 | ||
| 69 | return new SymSpellSpeller( builder.build() ); | |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * Prevent direct instantiation so that only the {@link SpellChecker} | |
| 74 | * interface | |
| 75 | * is available. | |
| 76 | * | |
| 77 | * @param symSpell The implementation-specific spell checker. | |
| 78 | */ | |
| 79 | private SymSpellSpeller( final SymSpell symSpell ) { | |
| 80 | mSymSpell = symSpell; | |
| 81 | } | |
| 82 | ||
| 83 | @Override | |
| 84 | public boolean inLexicon( final String lexeme ) { | |
| 85 | return lookup( lexeme, CLOSEST ).size() == 1; | |
| 86 | } | |
| 87 | ||
| 88 | @Override | |
| 89 | public List<String> suggestions( final String lexeme, int count ) { | |
| 90 | final List<String> result = new ArrayList<>( count ); | |
| 91 | ||
| 92 | for( final var item : lookup( lexeme, ALL ) ) { | |
| 93 | if( count-- > 0 ) { | |
| 94 | result.add( item.getSuggestion() ); | |
| 95 | } | |
| 96 | else { | |
| 97 | break; | |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | return result; | |
| 102 | } | |
| 103 | ||
| 104 | @Override | |
| 105 | public void proofread( | |
| 106 | final String text, final SpellCheckListener consumer ) { | |
| 107 | assert text != null; | |
| 108 | assert consumer != null; | |
| 109 | ||
| 110 | mBreakIterator.setText( text ); | |
| 111 | ||
| 112 | int boundaryIndex = mBreakIterator.first(); | |
| 113 | int previousIndex = 0; | |
| 114 | ||
| 115 | while( boundaryIndex != BreakIterator.DONE ) { | |
| 116 | final var lex = text.substring( previousIndex, boundaryIndex ) | |
| 117 | .toLowerCase(); | |
| 118 | ||
| 119 | // Get the lexeme for the possessive. | |
| 120 | final var pos = lex.endsWith( "'s" ) || lex.endsWith( "’s" ); | |
| 121 | final var lexeme = pos ? lex.substring( 0, lex.length() - 2 ) : lex; | |
| 122 | ||
| 123 | if( isWord( lexeme ) && !inLexicon( lexeme ) ) { | |
| 124 | consumer.accept( lex, previousIndex, boundaryIndex ); | |
| 125 | } | |
| 126 | ||
| 127 | previousIndex = boundaryIndex; | |
| 128 | boundaryIndex = mBreakIterator.next(); | |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Answers whether the given string is likely a word by checking the first | |
| 134 | * character. | |
| 135 | * | |
| 136 | * @param word The word to check. | |
| 137 | * @return {@code true} if the word begins with a letter. | |
| 138 | */ | |
| 139 | private boolean isWord( final String word ) { | |
| 140 | return !word.isEmpty() && isLetter( word.charAt( 0 ) ); | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * Returns a list of {@link SuggestItem} instances that provide alternative | |
| 145 | * spellings for the given lexeme. | |
| 146 | * | |
| 147 | * @param lexeme A word to look up in the lexicon. | |
| 148 | * @param v Influences the number of results returned. | |
| 149 | * @return Alternative lexemes. | |
| 150 | */ | |
| 151 | private List<SuggestItem> lookup( final String lexeme, final Verbosity v ) { | |
| 152 | return getSpeller().lookup( lexeme, v ); | |
| 153 | } | |
| 154 | ||
| 155 | private SymSpell getSpeller() { | |
| 156 | return mSymSpell; | |
| 157 | } | |
| 158 | } | |
| 1 | 159 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import de.jensd.fx.glyphs.GlyphIcons; | |
| 30 | import javafx.beans.value.ObservableBooleanValue; | |
| 31 | import javafx.event.ActionEvent; | |
| 32 | import javafx.event.EventHandler; | |
| 33 | import javafx.scene.input.KeyCombination; | |
| 34 | ||
| 35 | /** | |
| 36 | * Defines actions the user can take by interacting with the GUI. | |
| 37 | */ | |
| 38 | public class Action { | |
| 39 | public final String text; | |
| 40 | public final KeyCombination accelerator; | |
| 41 | public final GlyphIcons icon; | |
| 42 | public final EventHandler<ActionEvent> action; | |
| 43 | public final ObservableBooleanValue disable; | |
| 44 | ||
| 45 | public Action( | |
| 46 | final String text, | |
| 47 | final String accelerator, | |
| 48 | final GlyphIcons icon, | |
| 49 | final EventHandler<ActionEvent> action, | |
| 50 | final ObservableBooleanValue disable ) { | |
| 51 | ||
| 52 | this.text = text; | |
| 53 | this.accelerator = accelerator == null ? | |
| 54 | null : KeyCombination.valueOf( accelerator ); | |
| 55 | this.icon = icon; | |
| 56 | this.action = action; | |
| 57 | this.disable = disable; | |
| 58 | } | |
| 59 | } | |
| 1 | 60 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import com.scrivenvar.Messages; | |
| 31 | import de.jensd.fx.glyphs.GlyphIcons; | |
| 32 | import javafx.beans.value.ObservableBooleanValue; | |
| 33 | import javafx.event.ActionEvent; | |
| 34 | import javafx.event.EventHandler; | |
| 35 | ||
| 36 | /** | |
| 37 | * Provides a fluent interface around constructing actions so that duplication | |
| 38 | * can be avoided. | |
| 39 | */ | |
| 40 | public class ActionBuilder { | |
| 41 | private String mText; | |
| 42 | private String mAccelerator; | |
| 43 | private GlyphIcons mIcon; | |
| 44 | private EventHandler<ActionEvent> mAction; | |
| 45 | private ObservableBooleanValue mDisable; | |
| 46 | ||
| 47 | /** | |
| 48 | * Sets the action text based on a resource bundle key. | |
| 49 | * | |
| 50 | * @param key The key to look up in the {@link Messages}. | |
| 51 | * @return The corresponding value, or the key name if none found. | |
| 52 | */ | |
| 53 | public ActionBuilder setText( final String key ) { | |
| 54 | mText = Messages.get( key, key ); | |
| 55 | return this; | |
| 56 | } | |
| 57 | ||
| 58 | public ActionBuilder setAccelerator( final String accelerator ) { | |
| 59 | mAccelerator = accelerator; | |
| 60 | return this; | |
| 61 | } | |
| 62 | ||
| 63 | public ActionBuilder setIcon( final GlyphIcons icon ) { | |
| 64 | mIcon = icon; | |
| 65 | return this; | |
| 66 | } | |
| 67 | ||
| 68 | public ActionBuilder setAction( final EventHandler<ActionEvent> action ) { | |
| 69 | mAction = action; | |
| 70 | return this; | |
| 71 | } | |
| 72 | ||
| 73 | public ActionBuilder setDisable( final ObservableBooleanValue disable ) { | |
| 74 | mDisable = disable; | |
| 75 | return this; | |
| 76 | } | |
| 77 | ||
| 78 | public Action build() { | |
| 79 | return new Action( mText, mAccelerator, mIcon, mAction, mDisable ); | |
| 80 | } | |
| 81 | } | |
| 1 | 82 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory; | |
| 30 | import javafx.scene.Node; | |
| 31 | import javafx.scene.control.Button; | |
| 32 | import javafx.scene.control.Menu; | |
| 33 | import javafx.scene.control.MenuItem; | |
| 34 | import javafx.scene.control.Separator; | |
| 35 | import javafx.scene.control.SeparatorMenuItem; | |
| 36 | import javafx.scene.control.ToolBar; | |
| 37 | import javafx.scene.control.Tooltip; | |
| 38 | ||
| 39 | /** | |
| 40 | * Responsible for creating menu items and toolbar buttons. | |
| 41 | */ | |
| 42 | public class ActionUtils { | |
| 43 | ||
| 44 | public static Menu createMenu( final String text, final Action... actions ) { | |
| 45 | return new Menu( text, null, createMenuItems( actions ) ); | |
| 46 | } | |
| 47 | ||
| 48 | public static MenuItem[] createMenuItems( final Action... actions ) { | |
| 49 | final MenuItem[] menuItems = new MenuItem[ actions.length ]; | |
| 50 | ||
| 51 | for( int i = 0; i < actions.length; i++ ) { | |
| 52 | menuItems[ i ] = (actions[ i ] == null) | |
| 53 | ? new SeparatorMenuItem() | |
| 54 | : createMenuItem( actions[ i ] ); | |
| 55 | } | |
| 56 | ||
| 57 | return menuItems; | |
| 58 | } | |
| 59 | ||
| 60 | public static MenuItem createMenuItem( final Action action ) { | |
| 61 | final MenuItem menuItem = new MenuItem( action.text ); | |
| 62 | ||
| 63 | if( action.accelerator != null ) { | |
| 64 | menuItem.setAccelerator( action.accelerator ); | |
| 65 | } | |
| 66 | ||
| 67 | if( action.icon != null ) { | |
| 68 | menuItem.setGraphic( | |
| 69 | FontAwesomeIconFactory.get().createIcon( action.icon ) ); | |
| 70 | } | |
| 71 | ||
| 72 | menuItem.setOnAction( action.action ); | |
| 73 | ||
| 74 | if( action.disable != null ) { | |
| 75 | menuItem.disableProperty().bind( action.disable ); | |
| 76 | } | |
| 77 | ||
| 78 | menuItem.setMnemonicParsing( true ); | |
| 79 | ||
| 80 | return menuItem; | |
| 81 | } | |
| 82 | ||
| 83 | public static ToolBar createToolBar( final Action... actions ) { | |
| 84 | return new ToolBar( createToolBarButtons( actions ) ); | |
| 85 | } | |
| 86 | ||
| 87 | public static Node[] createToolBarButtons( final Action... actions ) { | |
| 88 | Node[] buttons = new Node[ actions.length ]; | |
| 89 | for( int i = 0; i < actions.length; i++ ) { | |
| 90 | buttons[ i ] = (actions[ i ] != null) | |
| 91 | ? createToolBarButton( actions[ i ] ) | |
| 92 | : new Separator(); | |
| 93 | } | |
| 94 | return buttons; | |
| 95 | } | |
| 96 | ||
| 97 | public static Button createToolBarButton( final Action action ) { | |
| 98 | final Button button = new Button(); | |
| 99 | button.setGraphic( | |
| 100 | FontAwesomeIconFactory | |
| 101 | .get() | |
| 102 | .createIcon( action.icon, "1.2em" ) ); | |
| 103 | ||
| 104 | String tooltip = action.text; | |
| 105 | ||
| 106 | if( tooltip.endsWith( "..." ) ) { | |
| 107 | tooltip = tooltip.substring( 0, tooltip.length() - 3 ); | |
| 108 | } | |
| 109 | ||
| 110 | if( action.accelerator != null ) { | |
| 111 | tooltip += " (" + action.accelerator.getDisplayText() + ')'; | |
| 112 | } | |
| 113 | ||
| 114 | button.setTooltip( new Tooltip( tooltip ) ); | |
| 115 | button.setFocusTraversable( false ); | |
| 116 | button.setOnAction( action.action ); | |
| 117 | ||
| 118 | if( action.disable != null ) { | |
| 119 | button.disableProperty().bind( action.disable ); | |
| 120 | } | |
| 121 | ||
| 122 | return button; | |
| 123 | } | |
| 124 | } | |
| 1 | 125 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import java.util.LinkedHashMap; | |
| 31 | import java.util.Map; | |
| 32 | ||
| 33 | /** | |
| 34 | * A map that removes the oldest entry once its capacity (cache size) has | |
| 35 | * been reached. | |
| 36 | * | |
| 37 | * @param <K> The type of key mapped to a value. | |
| 38 | * @param <V> The type of value mapped to a key. | |
| 39 | */ | |
| 40 | public class BoundedCache<K, V> extends LinkedHashMap<K, V> { | |
| 41 | private final int mCacheSize; | |
| 42 | ||
| 43 | /** | |
| 44 | * Constructs a new instance having a finite size. | |
| 45 | * | |
| 46 | * @param cacheSize The maximum number of entries. | |
| 47 | */ | |
| 48 | public BoundedCache( final int cacheSize ) { | |
| 49 | mCacheSize = cacheSize; | |
| 50 | } | |
| 51 | ||
| 52 | @Override | |
| 53 | protected boolean removeEldestEntry( | |
| 54 | final Map.Entry<K, V> eldest ) { | |
| 55 | return size() > mCacheSize; | |
| 56 | } | |
| 57 | } | |
| 1 | 58 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import java.io.File; | |
| 31 | import java.net.MalformedURLException; | |
| 32 | import java.net.URI; | |
| 33 | import java.net.URL; | |
| 34 | ||
| 35 | import static com.scrivenvar.util.ProtocolScheme.UNKNOWN; | |
| 36 | ||
| 37 | /** | |
| 38 | * Responsible for determining the protocol of a resource. | |
| 39 | */ | |
| 40 | public class ProtocolResolver { | |
| 41 | /** | |
| 42 | * Returns the protocol for a given URI or filename. | |
| 43 | * | |
| 44 | * @param resource Determine the protocol for this URI or filename. | |
| 45 | * @return The protocol for the given resource. | |
| 46 | */ | |
| 47 | public static ProtocolScheme getProtocol( final String resource ) { | |
| 48 | String protocol; | |
| 49 | ||
| 50 | try { | |
| 51 | final URI uri = new URI( resource ); | |
| 52 | ||
| 53 | if( uri.isAbsolute() ) { | |
| 54 | protocol = uri.getScheme(); | |
| 55 | } | |
| 56 | else { | |
| 57 | final URL url = new URL( resource ); | |
| 58 | protocol = url.getProtocol(); | |
| 59 | } | |
| 60 | } catch( final Exception e ) { | |
| 61 | // Could be HTTP, HTTPS? | |
| 62 | if( resource.startsWith( "//" ) ) { | |
| 63 | throw new IllegalArgumentException( "Relative context: " + resource ); | |
| 64 | } | |
| 65 | else { | |
| 66 | final File file = new File( resource ); | |
| 67 | protocol = getProtocol( file ); | |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | return ProtocolScheme.valueFrom( protocol ); | |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Returns the protocol for a given file. | |
| 76 | * | |
| 77 | * @param file Determine the protocol for this file. | |
| 78 | * @return The protocol for the given file. | |
| 79 | */ | |
| 80 | private static String getProtocol( final File file ) { | |
| 81 | String result; | |
| 82 | ||
| 83 | try { | |
| 84 | result = file.toURI().toURL().getProtocol(); | |
| 85 | } catch( final MalformedURLException ex ) { | |
| 86 | // Value guaranteed to avoid identification as a standard protocol. | |
| 87 | result = UNKNOWN.toString(); | |
| 88 | } | |
| 89 | ||
| 90 | return result; | |
| 91 | } | |
| 92 | } | |
| 1 | 93 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | /** | |
| 31 | * Represents the type of data encoding scheme used for a universal resource | |
| 32 | * indicator. | |
| 33 | */ | |
| 34 | public enum ProtocolScheme { | |
| 35 | /** | |
| 36 | * Denotes either HTTP or HTTPS. | |
| 37 | */ | |
| 38 | HTTP, | |
| 39 | /** | |
| 40 | * Denotes a local file. | |
| 41 | */ | |
| 42 | FILE, | |
| 43 | /** | |
| 44 | * Could not determine schema (or is not supported by the application). | |
| 45 | */ | |
| 46 | UNKNOWN; | |
| 47 | ||
| 48 | /** | |
| 49 | * Answers {@code true} if the given protocol is either HTTP or HTTPS. | |
| 50 | * | |
| 51 | * @return {@code true} the protocol is either HTTP or HTTPS. | |
| 52 | */ | |
| 53 | public boolean isHttp() { | |
| 54 | return this == HTTP; | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Answers {@code true} if the given protocol is for a local file. | |
| 59 | * | |
| 60 | * @return {@code true} the protocol is for a local file reference. | |
| 61 | */ | |
| 62 | public boolean isFile() { | |
| 63 | return this == FILE; | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Determines the protocol scheme for a given string. | |
| 68 | * | |
| 69 | * @param protocol A string representing data encoding protocol scheme. | |
| 70 | * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a | |
| 71 | * valid value from this enumeration. | |
| 72 | */ | |
| 73 | public static ProtocolScheme valueFrom( String protocol ) { | |
| 74 | ProtocolScheme result = UNKNOWN; | |
| 75 | protocol = sanitize( protocol ); | |
| 76 | ||
| 77 | for( final var scheme : values() ) { | |
| 78 | // This will match HTTP/HTTPS as well as FILE*, which may be inaccurate. | |
| 79 | if( scheme.name().startsWith( protocol ) ) { | |
| 80 | result = scheme; | |
| 81 | break; | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | return result; | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Returns an empty string if the given string to sanitize is {@code null}, | |
| 90 | * otherwise the given string in uppercase. Uppercase is used to align with | |
| 91 | * the enum name. | |
| 92 | * | |
| 93 | * @param s The string to sanitize, may be {@code null}. | |
| 94 | * @return A non-{@code null} string. | |
| 95 | */ | |
| 96 | private static String sanitize( final String s ) { | |
| 97 | return s == null ? "" : s.toUpperCase(); | |
| 98 | } | |
| 99 | } | |
| 1 | 100 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.util; | |
| 29 | ||
| 30 | import java.io.IOException; | |
| 31 | import java.net.URISyntaxException; | |
| 32 | import java.nio.file.*; | |
| 33 | import java.util.function.Consumer; | |
| 34 | ||
| 35 | import static java.nio.file.FileSystems.newFileSystem; | |
| 36 | import static java.util.Collections.emptyMap; | |
| 37 | ||
| 38 | /** | |
| 39 | * Responsible for finding file resources. | |
| 40 | */ | |
| 41 | public class ResourceWalker { | |
| 42 | private static final PathMatcher PATH_MATCHER = | |
| 43 | FileSystems.getDefault().getPathMatcher( "glob:**.{ttf,otf}" ); | |
| 44 | ||
| 45 | /** | |
| 46 | * @param dirName The root directory to scan for files matching the glob. | |
| 47 | * @param c The consumer function to call for each matching path found. | |
| 48 | * @throws URISyntaxException Could not convert the resource to a URI. | |
| 49 | * @throws IOException Could not walk the tree. | |
| 50 | */ | |
| 51 | public static void walk( final String dirName, final Consumer<Path> c ) | |
| 52 | throws URISyntaxException, IOException { | |
| 53 | final var resource = ResourceWalker.class.getResource( dirName ); | |
| 54 | ||
| 55 | if( resource != null ) { | |
| 56 | final var uri = resource.toURI(); | |
| 57 | final var path = uri.getScheme().equals( "jar" ) | |
| 58 | ? newFileSystem( uri, emptyMap() ).getPath( dirName ) | |
| 59 | : Paths.get( uri ); | |
| 60 | final var walk = Files.walk( path, 10 ); | |
| 61 | ||
| 62 | for( final var it = walk.iterator(); it.hasNext(); ) { | |
| 63 | final Path p = it.next(); | |
| 64 | if( PATH_MATCHER.matches( p ) ) { | |
| 65 | c.accept( p ); | |
| 66 | } | |
| 67 | } | |
| 68 | } | |
| 69 | } | |
| 70 | } | |
| 1 | 71 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import java.util.prefs.Preferences; | |
| 30 | ||
| 31 | import javafx.application.Platform; | |
| 32 | import javafx.scene.shape.Rectangle; | |
| 33 | import javafx.stage.Stage; | |
| 34 | import javafx.stage.WindowEvent; | |
| 35 | ||
| 36 | /** | |
| 37 | * Saves and restores Stage state (window bounds, maximized, fullScreen). | |
| 38 | */ | |
| 39 | public class StageState { | |
| 40 | ||
| 41 | public static final String K_PANE_SPLIT_DEFINITION = "pane.split.definition"; | |
| 42 | public static final String K_PANE_SPLIT_EDITOR = "pane.split.editor"; | |
| 43 | public static final String K_PANE_SPLIT_PREVIEW = "pane.split.preview"; | |
| 44 | ||
| 45 | private final Stage mStage; | |
| 46 | private final Preferences mState; | |
| 47 | ||
| 48 | private Rectangle normalBounds; | |
| 49 | private boolean runLaterPending; | |
| 50 | ||
| 51 | public StageState( final Stage stage, final Preferences state ) { | |
| 52 | mStage = stage; | |
| 53 | mState = state; | |
| 54 | ||
| 55 | restore(); | |
| 56 | ||
| 57 | stage.addEventHandler( WindowEvent.WINDOW_HIDING, e -> save() ); | |
| 58 | ||
| 59 | stage.xProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 60 | stage.yProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 61 | stage.widthProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 62 | stage.heightProperty().addListener( ( ob, o, n ) -> boundsChanged() ); | |
| 63 | } | |
| 64 | ||
| 65 | private void save() { | |
| 66 | final Rectangle bounds = isNormalState() ? getStageBounds() : normalBounds; | |
| 67 | ||
| 68 | if( bounds != null ) { | |
| 69 | mState.putDouble( "windowX", bounds.getX() ); | |
| 70 | mState.putDouble( "windowY", bounds.getY() ); | |
| 71 | mState.putDouble( "windowWidth", bounds.getWidth() ); | |
| 72 | mState.putDouble( "windowHeight", bounds.getHeight() ); | |
| 73 | } | |
| 74 | ||
| 75 | mState.putBoolean( "windowMaximized", mStage.isMaximized() ); | |
| 76 | mState.putBoolean( "windowFullScreen", mStage.isFullScreen() ); | |
| 77 | } | |
| 78 | ||
| 79 | private void restore() { | |
| 80 | final double x = mState.getDouble( "windowX", Double.NaN ); | |
| 81 | final double y = mState.getDouble( "windowY", Double.NaN ); | |
| 82 | final double w = mState.getDouble( "windowWidth", Double.NaN ); | |
| 83 | final double h = mState.getDouble( "windowHeight", Double.NaN ); | |
| 84 | final boolean maximized = mState.getBoolean( "windowMaximized", false ); | |
| 85 | final boolean fullScreen = mState.getBoolean( "windowFullScreen", false ); | |
| 86 | ||
| 87 | if( !Double.isNaN( x ) && !Double.isNaN( y ) ) { | |
| 88 | mStage.setX( x ); | |
| 89 | mStage.setY( y ); | |
| 90 | } // else: default behavior is center on screen | |
| 91 | ||
| 92 | if( !Double.isNaN( w ) && !Double.isNaN( h ) ) { | |
| 93 | mStage.setWidth( w ); | |
| 94 | mStage.setHeight( h ); | |
| 95 | } // else: default behavior is use scene size | |
| 96 | ||
| 97 | if( fullScreen != mStage.isFullScreen() ) { | |
| 98 | mStage.setFullScreen( fullScreen ); | |
| 99 | } | |
| 100 | ||
| 101 | if( maximized != mStage.isMaximized() ) { | |
| 102 | mStage.setMaximized( maximized ); | |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | /** | |
| 107 | * Remembers the window bounds when the window is not iconified, maximized or | |
| 108 | * in fullScreen. | |
| 109 | */ | |
| 110 | private void boundsChanged() { | |
| 111 | // avoid too many (and useless) runLater() invocations | |
| 112 | if( runLaterPending ) { | |
| 113 | return; | |
| 114 | } | |
| 115 | ||
| 116 | runLaterPending = true; | |
| 117 | ||
| 118 | // must use runLater() to ensure that change of all properties | |
| 119 | // (x, y, width, height, iconified, maximized and fullScreen) | |
| 120 | // has finished | |
| 121 | Platform.runLater( () -> { | |
| 122 | runLaterPending = false; | |
| 123 | ||
| 124 | if( isNormalState() ) { | |
| 125 | normalBounds = getStageBounds(); | |
| 126 | } | |
| 127 | } ); | |
| 128 | } | |
| 129 | ||
| 130 | private boolean isNormalState() { | |
| 131 | return !mStage.isIconified() && | |
| 132 | !mStage.isMaximized() && | |
| 133 | !mStage.isFullScreen(); | |
| 134 | } | |
| 135 | ||
| 136 | private Rectangle getStageBounds() { | |
| 137 | return new Rectangle( | |
| 138 | mStage.getX(), | |
| 139 | mStage.getY(), | |
| 140 | mStage.getWidth(), | |
| 141 | mStage.getHeight() | |
| 142 | ); | |
| 143 | } | |
| 144 | } | |
| 1 | 145 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.scrivenvar.util; | |
| 28 | ||
| 29 | import java.util.ArrayList; | |
| 30 | import java.util.prefs.Preferences; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for trimming, storing, and retrieving strings. | |
| 34 | */ | |
| 35 | public class Utils { | |
| 36 | ||
| 37 | public static String ltrim( final String s ) { | |
| 38 | int i = 0; | |
| 39 | ||
| 40 | while( i < s.length() && Character.isWhitespace( s.charAt( i ) ) ) { | |
| 41 | i++; | |
| 42 | } | |
| 43 | ||
| 44 | return s.substring( i ); | |
| 45 | } | |
| 46 | ||
| 47 | public static String rtrim( final String s ) { | |
| 48 | int i = s.length() - 1; | |
| 49 | ||
| 50 | while( i >= 0 && Character.isWhitespace( s.charAt( i ) ) ) { | |
| 51 | i--; | |
| 52 | } | |
| 53 | ||
| 54 | return s.substring( 0, i + 1 ); | |
| 55 | } | |
| 56 | ||
| 57 | public static String[] getPrefsStrings( final Preferences prefs, | |
| 58 | String key ) { | |
| 59 | final ArrayList<String> arr = new ArrayList<>( 256 ); | |
| 60 | ||
| 61 | for( int i = 0; i < 10000; i++ ) { | |
| 62 | final String s = prefs.get( key + (i + 1), null ); | |
| 63 | ||
| 64 | if( s == null ) { | |
| 65 | break; | |
| 66 | } | |
| 67 | ||
| 68 | arr.add( s ); | |
| 69 | } | |
| 70 | ||
| 71 | return arr.toArray( new String[ 0 ] ); | |
| 72 | } | |
| 73 | ||
| 74 | public static void putPrefsStrings( Preferences prefs, String key, | |
| 75 | String[] strings ) { | |
| 76 | for( int i = 0; i < strings.length; i++ ) { | |
| 77 | prefs.put( key + (i + 1), strings[ i ] ); | |
| 78 | } | |
| 79 | ||
| 80 | for( int i = strings.length; prefs.get( key + (i + 1), | |
| 81 | null ) != null; i++ ) { | |
| 82 | prefs.remove( key + (i + 1) ); | |
| 83 | } | |
| 84 | } | |
| 85 | } | |
| 1 | 86 |
| 1 | # R Scripts | |
| 2 | ||
| 3 | These R scripts illustrate how R can be used within an application to perform calculations using variables. Authors are free to write their own scripts, of course. These scripts serve as an example of how to automate certain tasks while writing. | |
| 4 | ||
| 5 | ## Configuration | |
| 6 | ||
| 7 | Configure the editor to use the R scripts as follows: | |
| 8 | ||
| 9 | 1. Copy the R scripts into same directory as your Markdown files. | |
| 10 | 1. Start the editor. | |
| 11 | 1. Click **Tools → R Script**. | |
| 12 | 1. Copy and paste the following: | |
| 13 | ||
| 14 | assign( 'anchor', as.Date( '$date.anchor$', format='%Y-%m-%d' ), envir = .GlobalEnv ); | |
| 15 | setwd( '$application.r.working.directory$' ); | |
| 16 | source( 'pluralize.R' ); | |
| 17 | source( 'csv.R' ); | |
| 18 | source( 'conversion.R' ); | |
| 19 | ||
| 20 | 1. Click **File → New** to create a new file. | |
| 21 | 1. Click **File → Save As** to set a filename. | |
| 22 | 1. Set **Name** to: `variables.yaml` | |
| 23 | 1. Click **OK**. | |
| 24 | 1. Paste the following definitions: | |
| 25 | ||
| 26 | date: | |
| 27 | anchor: 2017-01-01 | |
| 28 | editor: | |
| 29 | examples: | |
| 30 | season: 2017-09-02 | |
| 31 | math: | |
| 32 | x: 1 | |
| 33 | y: $editor.examples.math.x$ + 1 | |
| 34 | z: $editor.examples.math.y$ + 1 | |
| 35 | name: | |
| 36 | given: Josephene | |
| 37 | ||
| 38 | 1. Save and close the file. | |
| 39 | 1. Click **File → Open** | |
| 40 | 1. Change **Markdown Files** to **Definition Files**. | |
| 41 | 1. Select `variables.yaml`. | |
| 42 | 1. Click **Open**. | |
| 43 | ||
| 44 | R functionality is configured. | |
| 45 | ||
| 46 | ## Definitions | |
| 47 | ||
| 48 | The variables definitions within `variables.yaml` are available to R using the R syntax. An additional variable, `application.r.working.directory` is added to the list of variables. The value is set to the working directory of the file being edited. Hover the mouse cursor over the file tab in the editor to see the full path to the file. | |
| 49 | ||
| 50 | ## Examples | |
| 51 | ||
| 52 | This section demonstrates how to use the R functions when editing. Complete the following steps to begin: | |
| 53 | ||
| 54 | 1. Click **File → New** to create a new file. | |
| 55 | 1. Click **File → Save As** to set a filename. | |
| 56 | 1. Set **Name** to: `example.Rmd` | |
| 57 | 1. Click **OK**. | |
| 58 | ||
| 59 | The examples are ready for use within the editor. | |
| 60 | ||
| 61 | ### Arithmetic | |
| 62 | ||
| 63 | Type the following to perform a simple calculation: | |
| 64 | ||
| 65 | `r# 1+1` | |
| 66 | ||
| 67 | The preview pane shows `2.0`. | |
| 68 | ||
| 69 | ### Functions | |
| 70 | ||
| 71 | Call the [format](https://stat.ethz.ch/R-manual/R-devel/library/base/html/format.html) function to truncate unwanted decimal places as follows: | |
| 72 | ||
| 73 | `r# format(1+1,digits=1)` | |
| 74 | ||
| 75 | The preview pane shows `2`. | |
| 76 | ||
| 77 | ### Pluralize | |
| 78 | ||
| 79 | Many English words can be pluralized as follows: | |
| 80 | ||
| 81 | `r# pl('wolf',2)` | |
| 82 | ||
| 83 | The preview pane shows `wolves`. The `pluralize.R` file contains a partial implementation of Damian Conway's algorithmic approach to English pluralization. | |
| 84 | ||
| 85 | ### Chicago Manual of Style | |
| 86 | ||
| 87 | Apply the Chicago Manual of Style for words less than one-hundred as follows: | |
| 88 | ||
| 89 | `r# cms(1)` `r# cms(99)` `r# cms(101)` | |
| 90 | ||
| 91 | The preview pane shows numbers written out as `one` and `ninety-nine`, followed by the digits 101. | |
| 92 | ||
| 93 | ### Data Import | |
| 94 | ||
| 95 | Import and display information from a CSV file as follows: | |
| 96 | ||
| 97 | 1. Click **File → New** to create a new file. | |
| 98 | 1. Click **File → Save As** to rename the file. | |
| 99 | 1. Set the filename to: `data.csv` | |
| 100 | 1. Paste the following into `data.csv`: | |
| 101 | ||
| 102 | Animal,Quantity,Country | |
| 103 | Aardwolf,1,Africa | |
| 104 | Keel-billed toucan,1,Belize | |
| 105 | Beaver,2,Canada | |
| 106 | Mute swan,3,Denmark | |
| 107 | Lion,5,Ethiopia | |
| 108 | Brown bear,8,Finland | |
| 109 | Dolphin,13,Greece | |
| 110 | Turul,21,Hungary | |
| 111 | Gyrfalcon,34,Iceland | |
| 112 | Red-billed streamertail,55,Jamaica | |
| 113 | ||
| 114 | 1. Click the `example.Rmd` tab. | |
| 115 | 1. Type the following: | |
| 116 | ||
| 117 | `r# csv2md('data.csv',total=F)` | |
| 118 | ||
| 119 | 1. Type the following to calculate a total for all numeric columns: | |
| 120 | ||
| 121 | `r# csv2md('data.csv')` | |
| 122 | ||
| 123 | This imports the data from an external file and formats the information into a table, automatically. Update the data as follows: | |
| 124 | ||
| 125 | 1. Click the `data.csv` tab to edit the data. | |
| 126 | 1. Change the data by adding a new row. | |
| 127 | 1. Save the file. | |
| 128 | 1. Click the `example.Rmd` tab. | |
| 129 | ||
| 130 | The preview pane shows the revised contents. | |
| 131 | ||
| 132 | ### Elapsed Time | |
| 133 | ||
| 134 | The duration of a timeline, given in numbers of days, can be computed into English as follows: | |
| 135 | ||
| 136 | `r# elapsed(1,1)` | |
| 137 | ||
| 138 | The preview pane shows `same day`. Change the expression to: | |
| 139 | ||
| 140 | `r# elapsed(1,2)` | |
| 141 | ||
| 142 | The preview pane shows `one day`. Change the expression to: | |
| 143 | ||
| 144 | `r# elapsed(1,112358)` | |
| 145 | ||
| 146 | The preview pane shows `307 years, seven months, and sixteen days`, combined using the Chicago Manual of Style, the pluralization function, and a [serial comma](https://www.behance.net/gallery/19417363/The-Oxford-Comma). | |
| 147 | ||
| 148 | ### Variable Syntax | |
| 149 | ||
| 150 | The syntax for a variable changes when using an R Markdown file (denoted by the `.Rmd` filename extension), as opposed to a regular Markdown file (`.md`). Return to the example file and type the following: | |
| 151 | ||
| 152 | `r# v$date$anchor` | |
| 153 | ||
| 154 | The preview pane shows the date. | |
| 155 | ||
| 156 | ### Autocomplete | |
| 157 | ||
| 158 | Automatically insert a variable reference into the text as follows: | |
| 159 | ||
| 160 | 1. Type: `Jos` | |
| 161 | * Note the capital letter, matches are case sensitive. | |
| 162 | 1. Hold down the `Control` key. | |
| 163 | 1. Tap the `Spacebar` | |
| 164 | ||
| 165 | The editor shows: | |
| 166 | ||
| 167 | `r#x( v$editor$examples$name$given )` | |
| 168 | ||
| 169 | The preview pane shows: | |
| 170 | ||
| 171 | Josephine | |
| 172 | ||
| 173 | Here, the `x` function evaluates its parameter as an expression. This allows variables to include expressions in their definition. | |
| 174 | ||
| 175 | ### Variable Definition Expressions | |
| 176 | ||
| 177 | Definition file variables are have the ability to reference other definitions. Try the following: | |
| 178 | ||
| 179 | x = `r#x( v$editor$examples$math$x )`; | |
| 180 | y = `r#x( v$editor$examples$math$y )`; | |
| 181 | z = `r#x( v$editor$examples$math$z )` | |
| 182 | ||
| 183 | The preview pane shows: | |
| 184 | ||
| 185 | x = 1.0; y = 2.0; z = 3.0 | |
| 186 | ||
| 187 | ### Case | |
| 188 | ||
| 189 | Ensure words begin with a lowercase letter as follows: | |
| 190 | ||
| 191 | `r#lc( v$editor$examples$name$given )` | |
| 192 | ||
| 193 | The preview pane shows: | |
| 194 | ||
| 195 | josephine | |
| 196 | ||
| 197 | Similarly, ensure an uppercase letter as follows: | |
| 198 | ||
| 199 | `r#uc( 'hello, world!' )` | |
| 200 | ||
| 201 | The preview pane shows: | |
| 202 | ||
| 203 | Hello, world! | |
| 204 | ||
| 205 | ### Month | |
| 206 | ||
| 207 | Display the month name given a month number as follows: | |
| 208 | ||
| 209 | `r# month( 1 )` | |
| 210 | ||
| 211 | The preview pane shows: | |
| 212 | ||
| 213 | January | |
| 214 | ||
| 215 | ## Summary | |
| 216 | ||
| 217 | Authors can inline R statements into documents, directly, so long as those statements generate text. Plots, graphs, and images must be referenced as external image files or URLs. | |
| 1 | 218 |
| 1 | # ######################################################################## | |
| 2 | # | |
| 3 | # Substitute R expressions in a document with their evaluated value. The | |
| 4 | # anchor variable must be set for functions that use relative dates. | |
| 5 | # | |
| 6 | # ######################################################################## | |
| 7 | ||
| 8 | # Evaluates an expression; writes s if there is no expression. | |
| 9 | x <- function( s ) { | |
| 10 | return( | |
| 11 | tryCatch({ | |
| 12 | r = eval( parse( text=s ) ) | |
| 13 | ||
| 14 | # If the result isn't primitive, then it was probably parsed into | |
| 15 | # an unprintable object (e.g., "gray" becomes a colour). In those | |
| 16 | # cases, return the original text string. Otherwise, an atomic | |
| 17 | # value means a primitive type (string, integer, etc.) that can be | |
| 18 | # written directly into the document. | |
| 19 | # | |
| 20 | # See: http://stackoverflow.com/a/19501276/59087 | |
| 21 | if( is.atomic( r ) ) { | |
| 22 | r | |
| 23 | } | |
| 24 | else { | |
| 25 | s | |
| 26 | } | |
| 27 | }, | |
| 28 | warning = function( w ) { | |
| 29 | s | |
| 30 | }, | |
| 31 | error = function( e ) { | |
| 32 | s | |
| 33 | }) | |
| 34 | ) | |
| 35 | } | |
| 36 | ||
| 37 | # Returns a date offset by a given number of days, relative to the given | |
| 38 | # date (d). This does not use the anchor, but is used to get the anchor's | |
| 39 | # value as a date. | |
| 40 | when <- function( d, n = 0, format = "%Y-%m-%d" ) { | |
| 41 | as.Date( d, format = format ) + x( n ) | |
| 42 | } | |
| 43 | ||
| 44 | # Full date (s) offset by an optional number of days before or after. | |
| 45 | # This will remove leading zeros (applying leading spaces instead, which | |
| 46 | # are ignored by any worthwhile typesetting engine). | |
| 47 | annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) { | |
| 48 | format( when( anchor, days ), format = oformat ) | |
| 49 | } | |
| 50 | ||
| 51 | # Extracts the year from a date string. | |
| 52 | year <- function( days = 0, format = "%Y-%m-%d" ) { | |
| 53 | annal( days, format, "%Y" ) | |
| 54 | } | |
| 55 | ||
| 56 | # Day of the week (in days since the anchor date). | |
| 57 | weekday <- function( n ) { | |
| 58 | weekdays( when( anchor, n ) ) | |
| 59 | } | |
| 60 | ||
| 61 | # String concatenate function alias because paste0 is a terrible name. | |
| 62 | concat <- paste0 | |
| 63 | ||
| 64 | # Translates a number from digits to words using Chicago Manual of Style. | |
| 65 | # This does not translate numbers greater than one hundred. If ordinal | |
| 66 | # is TRUE, this will return the ordinal name. This will not produce ordinals | |
| 67 | # for numbers greater than 100 | |
| 68 | cms <- function( n, ordinal = FALSE ) { | |
| 69 | n <- x( n ) | |
| 70 | ||
| 71 | # We're done here. | |
| 72 | if( n == 0 ) { | |
| 73 | if( ordinal ) { | |
| 74 | return( "zeroth" ) | |
| 75 | } | |
| 76 | ||
| 77 | return( "zero" ) | |
| 78 | } | |
| 79 | ||
| 80 | # Concatenate this a little later. | |
| 81 | if( n < 0 ) { | |
| 82 | result = "negative " | |
| 83 | n = abs( n ) | |
| 84 | } | |
| 85 | ||
| 86 | # Do not spell out numbers greater than one hundred. | |
| 87 | if( n > 100 ) { | |
| 88 | # Comma-separated numbers. | |
| 89 | return( format( n, big.mark=",", trim=TRUE, scientific=FALSE ) ) | |
| 90 | } | |
| 91 | ||
| 92 | # Don't go beyond 100. | |
| 93 | if( n == 100 ) { | |
| 94 | if( ordinal ) { | |
| 95 | return( "one hundredth" ) | |
| 96 | } | |
| 97 | ||
| 98 | return( "one hundred" ) | |
| 99 | } | |
| 100 | ||
| 101 | # Samuel Langhorne Clemens noted English has too many exceptions. | |
| 102 | small = c( | |
| 103 | "one", "two", "three", "four", "five", | |
| 104 | "six", "seven", "eight", "nine", "ten", | |
| 105 | "eleven", "twelve", "thirteen", "fourteen", "fifteen", | |
| 106 | "sixteen", "seventeen", "eighteen", "nineteen" | |
| 107 | ) | |
| 108 | ||
| 109 | ord_small = c( | |
| 110 | "first", "second", "third", "fourth", "fifth", | |
| 111 | "sixth", "seventh", "eighth", "ninth", "tenth", | |
| 112 | "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", | |
| 113 | "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth" | |
| 114 | ) | |
| 115 | ||
| 116 | # After this, the number (n) is between 20 and 99. | |
| 117 | if( n < 20 ) { | |
| 118 | if( ordinal ) { | |
| 119 | return( .subset( ord_small, n %% 100 ) ) | |
| 120 | } | |
| 121 | ||
| 122 | return( .subset( small, n %% 100 ) ) | |
| 123 | } | |
| 124 | ||
| 125 | tens = c( "", | |
| 126 | "twenty", "thirty", "forty", "fifty", | |
| 127 | "sixty", "seventy", "eighty", "ninety" | |
| 128 | ) | |
| 129 | ||
| 130 | ord_tens = c( "", | |
| 131 | "twentieth", "thirtieth", "fortieth", "fiftieth", | |
| 132 | "sixtieth", "seventieth", "eightieth", "ninetieth" | |
| 133 | ) | |
| 134 | ||
| 135 | ones_index = n %% 10 | |
| 136 | n = n %/% 10 | |
| 137 | ||
| 138 | # No number in the ones column, so the number must be a multiple of ten. | |
| 139 | if( ones_index == 0 ) { | |
| 140 | if( ordinal ) { | |
| 141 | return( .subset( ord_tens, n ) ) | |
| 142 | } | |
| 143 | ||
| 144 | return( .subset( tens, n ) ) | |
| 145 | } | |
| 146 | ||
| 147 | # Find the value from the ones column. | |
| 148 | if( ordinal ) { | |
| 149 | unit_1 = .subset( ord_small, ones_index ) | |
| 150 | } | |
| 151 | else { | |
| 152 | unit_1 = .subset( small, ones_index ) | |
| 153 | } | |
| 154 | ||
| 155 | # Find the tens column. | |
| 156 | unit_10 = .subset( tens, n ) | |
| 157 | ||
| 158 | # Hyphenate the tens and the ones together. | |
| 159 | concat( unit_10, concat( "-", unit_1 ) ) | |
| 160 | } | |
| 161 | ||
| 162 | # Returns a human-readable string that provides the elapsed time between | |
| 163 | # two numbers in terms of years, months, and days. If any unit value is zero, | |
| 164 | # the unit is not included. The words (year, month, day) are pluralized | |
| 165 | # according to English grammar. The numbers are written out according to | |
| 166 | # Chicago Manual of Style. This applies the serial comma. | |
| 167 | # | |
| 168 | # Both numbers are offsets relative to the anchor date. | |
| 169 | # | |
| 170 | # If all unit values are zero, this returns s ("same day" by default). | |
| 171 | # | |
| 172 | # If the start date (began) is greater than end date (ended), the dates are | |
| 173 | # swapped before calculations are performed. This allows any two dates | |
| 174 | # to be compared and positive unit values are always returned. | |
| 175 | # | |
| 176 | elapsed <- function( began, ended, s = "same day" ) { | |
| 177 | began = when( anchor, began ) | |
| 178 | ended = when( anchor, ended ) | |
| 179 | ||
| 180 | # Swap the dates if the end date comes before the start date. | |
| 181 | if( as.integer( ended - began ) < 0 ) { | |
| 182 | tempd = began | |
| 183 | began = ended | |
| 184 | ended = tempd | |
| 185 | } | |
| 186 | ||
| 187 | # Calculate number of elapsed years. | |
| 188 | years = length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 189 | ||
| 190 | # Move the start date up by the number of elapsed years. | |
| 191 | if( years > 0 ) { | |
| 192 | began = seq( began, length = 2, by = concat( years, " years" ) )[2] | |
| 193 | years = pl.numeric( "year", years ) | |
| 194 | } | |
| 195 | else { | |
| 196 | # Zero years. | |
| 197 | years = "" | |
| 198 | } | |
| 199 | ||
| 200 | # Calculate number of elapsed months, excluding years. | |
| 201 | months = length( seq( from = began, to = ended, by = 'month' ) ) - 1 | |
| 202 | ||
| 203 | # Move the start date up by the number of elapsed months | |
| 204 | if( months > 0 ) { | |
| 205 | began = seq( began, length = 2, by = concat( months, " months" ) )[2] | |
| 206 | months = pl.numeric( "month", months ) | |
| 207 | } | |
| 208 | else { | |
| 209 | # Zero months | |
| 210 | months = "" | |
| 211 | } | |
| 212 | ||
| 213 | # Calculate number of elapsed days, excluding months and years. | |
| 214 | days = length( seq( from = began, to = ended, by = 'day' ) ) - 1 | |
| 215 | ||
| 216 | if( days > 0 ) { | |
| 217 | days = pl.numeric( "day", days ) | |
| 218 | } | |
| 219 | else { | |
| 220 | # Zero days | |
| 221 | days = "" | |
| 222 | } | |
| 223 | ||
| 224 | if( years <= 0 && months <= 0 && days <= 0 ) { | |
| 225 | return( s ) | |
| 226 | } | |
| 227 | ||
| 228 | # Put them all in a vector, then remove the empty values. | |
| 229 | s <- c( years, months, days ) | |
| 230 | s <- s[ s != "" ] | |
| 231 | ||
| 232 | r <- paste( s, collapse = ", " ) | |
| 233 | ||
| 234 | # If all three items are present, replace the last comma with ", and". | |
| 235 | if( length( s ) > 2 ) { | |
| 236 | return( gsub( "(.*),", "\\1, and", r ) ) | |
| 237 | } | |
| 238 | ||
| 239 | # Does nothing if no commas are present. | |
| 240 | gsub( "(.*),", "\\1 and", r ) | |
| 241 | } | |
| 242 | ||
| 243 | # Returns the number (n) in English followed by the plural or singular | |
| 244 | # form of the given string (s; resumably a noun), if applicable, according | |
| 245 | # to English grammar. That is, pl.numeric( "wolf", 5 ) will return | |
| 246 | # "five wolves". | |
| 247 | pl.numeric <- function( s, n ) { | |
| 248 | concat( cms( n ), concat( " ", pluralize( s, n ) ) ) | |
| 249 | } | |
| 250 | ||
| 251 | # Name of the season, starting with an capital letter. | |
| 252 | season <- function( n, format = "%Y-%m-%d" ) { | |
| 253 | WS <- as.Date("2016-12-15", "%Y-%m-%d") # Winter Solstice | |
| 254 | SE <- as.Date("2016-03-15", "%Y-%m-%d") # Spring Equinox | |
| 255 | SS <- as.Date("2016-06-15", "%Y-%m-%d") # Summer Solstice | |
| 256 | AE <- as.Date("2016-09-15", "%Y-%m-%d") # Autumn Equinox | |
| 257 | ||
| 258 | d <- when( anchor, n ) | |
| 259 | d <- as.Date( strftime( d, format="2016-%m-%d" ) ) | |
| 260 | ||
| 261 | ifelse( d >= WS | d < SE, "Winter", | |
| 262 | ifelse( d >= SE & d < SS, "Spring", | |
| 263 | ifelse( d >= SS & d < AE, "Summer", "Autumn" ) | |
| 264 | ) | |
| 265 | ) | |
| 266 | } | |
| 267 | ||
| 268 | # Converts the first letter in a string to lowercase | |
| 269 | lc <- function( s ) { | |
| 270 | concat( tolower( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 271 | } | |
| 272 | ||
| 273 | # Converts the first letter in a string to uppercase | |
| 274 | uc <- function( s ) { | |
| 275 | concat( toupper( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 276 | } | |
| 277 | ||
| 278 | # Returns the number of days between the given dates. | |
| 279 | days <- function( d1, d2, format = "%Y-%m-%d" ) { | |
| 280 | dates = c( d1, d2 ) | |
| 281 | dt = strptime( dates, format = format ) | |
| 282 | as.integer( difftime( dates[2], dates[1], units = "days" ) ) | |
| 283 | } | |
| 284 | ||
| 285 | # Returns the number of years elapsed. | |
| 286 | years <- function( began, ended ) { | |
| 287 | began = when( anchor, began ) | |
| 288 | ended = when( anchor, ended ) | |
| 289 | ||
| 290 | # Swap the dates if the end date comes before the start date. | |
| 291 | if( as.integer( ended - began ) < 0 ) { | |
| 292 | tempd = began | |
| 293 | began = ended | |
| 294 | ended = tempd | |
| 295 | } | |
| 296 | ||
| 297 | # Calculate number of elapsed years. | |
| 298 | length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 299 | } | |
| 300 | ||
| 301 | # Full name of the month, starting with a capital letter. | |
| 302 | month <- function( n ) { | |
| 303 | # Faster than month.name[ x( n ) ] | |
| 304 | .subset( month.name, x( n ) ) | |
| 305 | } | |
| 306 | ||
| 307 | money <- function( n ) { | |
| 308 | formatC( x( n ), format="d" ) | |
| 309 | } | |
| 1 | 310 |
| 1 | # ###################################################################### | |
| 2 | # | |
| 3 | # Copyright 2016, White Magic Software, Ltd. | |
| 4 | # | |
| 5 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 6 | # a copy of this software and associated documentation files (the | |
| 7 | # "Software"), to deal in the Software without restriction, including | |
| 8 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 10 | # permit persons to whom the Software is furnished to do so, subject to | |
| 11 | # the following conditions: | |
| 12 | # | |
| 13 | # The above copyright notice and this permission notice shall be | |
| 14 | # included in all copies or substantial portions of the Software. | |
| 15 | # | |
| 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 23 | # | |
| 24 | # ###################################################################### | |
| 25 | ||
| 26 | # ###################################################################### | |
| 27 | # | |
| 28 | # Converts CSV to Markdown. | |
| 29 | # | |
| 30 | # ###################################################################### | |
| 31 | ||
| 32 | # Reads a CSV file and converts the contents to a Markdown table. The | |
| 33 | # file must be in the working directory as specified by setwd. | |
| 34 | # | |
| 35 | # @param f The filename to convert. | |
| 36 | # @param decimals Rounded decimal places (default 1). | |
| 37 | # @param totals Include total sums (default TRUE). | |
| 38 | # @param align Right-align numbers (default TRUE). | |
| 39 | csv2md <- function( f, decimals = 1, totals = T, align = T ) { | |
| 40 | # Read the CVS data from the file; ensure strings become characters. | |
| 41 | df <- read.table( f, sep=',', header=T, stringsAsFactors=F ) | |
| 42 | ||
| 43 | if( totals ) { | |
| 44 | # Determine what columns can be summed. | |
| 45 | number <- which( unlist( lapply( df, is.numeric ) ) ) | |
| 46 | ||
| 47 | # Use colSums when more than one summable column exists. | |
| 48 | if( length( number ) > 1 ) { | |
| 49 | f.sum <- colSums | |
| 50 | } | |
| 51 | else { | |
| 52 | f.sum <- sum | |
| 53 | } | |
| 54 | ||
| 55 | # Calculate the sum of all the summable columns and insert the | |
| 56 | # results back into the data frame. | |
| 57 | df[ (nrow( df ) + 1), number ] <- f.sum( df[, number], na.rm=TRUE ) | |
| 58 | ||
| 59 | # pluralize would be heavyweight here. | |
| 60 | if( length( number ) > 1 ) { | |
| 61 | t <- "**Totals**" | |
| 62 | } | |
| 63 | else { | |
| 64 | t <- "**Total**" | |
| 65 | } | |
| 66 | ||
| 67 | # Change the first column of the last line to "Total(s)". | |
| 68 | df[ nrow( df ), 1 ] <- t | |
| 69 | ||
| 70 | # Don't clutter the output with "NA" text. | |
| 71 | df[ is.na( df ) ] <- "" | |
| 72 | } | |
| 73 | ||
| 74 | if( align ) { | |
| 75 | is.char <- vapply( df, is.character, logical( 1 ) ) | |
| 76 | dashes <- paste( ifelse( is.char, ':---', '---:' ), collapse='|' ) | |
| 77 | } | |
| 78 | else { | |
| 79 | dashes <- paste( rep( '---', length( df ) ), collapse = '|') | |
| 80 | } | |
| 81 | ||
| 82 | # Create a Markdown version of the data frame. | |
| 83 | paste( | |
| 84 | paste( names( df ), collapse = '|'), '\n', | |
| 85 | dashes, '\n', | |
| 86 | paste( | |
| 87 | Reduce( function( x, y ) { | |
| 88 | paste( x, format( y, digits = decimals ), sep = '|' ) | |
| 89 | }, df | |
| 90 | ), | |
| 91 | collapse = '|\n', sep='' | |
| 92 | ) | |
| 93 | ) | |
| 94 | } | |
| 95 | ||
| 1 | 96 |
| 1 | # ###################################################################### | |
| 2 | # | |
| 3 | # Copyright 2016, White Magic Software, Ltd. | |
| 4 | # | |
| 5 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 6 | # a copy of this software and associated documentation files (the | |
| 7 | # "Software"), to deal in the Software without restriction, including | |
| 8 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 10 | # permit persons to whom the Software is furnished to do so, subject to | |
| 11 | # the following conditions: | |
| 12 | # | |
| 13 | # The above copyright notice and this permission notice shall be | |
| 14 | # included in all copies or substantial portions of the Software. | |
| 15 | # | |
| 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 23 | # | |
| 24 | # ###################################################################### | |
| 25 | ||
| 26 | # ###################################################################### | |
| 27 | # | |
| 28 | # See Damian Conway's "An Algorithmic Approach to English Pluralization": | |
| 29 | # http://goo.gl/oRL4MP | |
| 30 | # See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/ | |
| 31 | # See Shevek's Pluralizer: https://github.com/shevek/linguistics/ | |
| 32 | # See also: http://www.freevectors.net/assets/files/plural.txt | |
| 33 | # | |
| 34 | # ###################################################################### | |
| 35 | ||
| 36 | pluralize <- function( s, n ) { | |
| 37 | result <- s | |
| 38 | ||
| 39 | # Partial implementation of Conway's algorithm for nouns. | |
| 40 | if( n != 1 ) { | |
| 41 | if( pl.noninflective( s ) || | |
| 42 | pl.suffix( "fish", s ) || | |
| 43 | pl.suffix( "ois", s ) || | |
| 44 | pl.suffix( "sheep", s ) || | |
| 45 | pl.suffix( "deer", s ) || | |
| 46 | pl.suffix( "pox", s ) || | |
| 47 | pl.suffix( "[A-Z].*ese", s ) || | |
| 48 | pl.suffix( "itis", s ) ) { | |
| 49 | # 1. Retain non-inflective user-mapped noun as is. | |
| 50 | # 2. Retain non-inflective plural as is. | |
| 51 | result <- s | |
| 52 | } | |
| 53 | else if( pl.is.irregular.pl( s ) ) { | |
| 54 | # 4. Change irregular plurals based on mapping. | |
| 55 | result <- pl.irregular.pl( s ) | |
| 56 | } | |
| 57 | else if( pl.is.irregular.es( s ) ) { | |
| 58 | # x. From Shevek's Pluralizer | |
| 59 | result <- pl.inflect( s, "", "es" ) | |
| 60 | } | |
| 61 | else if( pl.suffix( "man", s ) ) { | |
| 62 | # 5. For -man, change -an to -en | |
| 63 | result <- pl.inflect( s, "an", "en" ) | |
| 64 | } | |
| 65 | else if( pl.suffix( "[lm]ouse", s ) ) { | |
| 66 | # 5. For [lm]ouse, change -ouse to -ice | |
| 67 | result <- pl.inflect( s, "ouse", "ice" ) | |
| 68 | } | |
| 69 | else if( pl.suffix( "tooth", s ) ) { | |
| 70 | # 5. For -tooth, change -ooth to -eeth | |
| 71 | result <- pl.inflect( s, "ooth", "eeth" ) | |
| 72 | } | |
| 73 | else if( pl.suffix( "goose", s ) ) { | |
| 74 | # 5. For -goose, change -oose to -eese | |
| 75 | result <- pl.inflect( s, "oose", "eese" ) | |
| 76 | } | |
| 77 | else if( pl.suffix( "foot", s ) ) { | |
| 78 | # 5. For -foot, change -oot to -eet | |
| 79 | result <- pl.inflect( s, "oot", "eet" ) | |
| 80 | } | |
| 81 | else if( pl.suffix( "zoon", s ) ) { | |
| 82 | # 5. For -zoon, change -on to -a | |
| 83 | result <- pl.inflect( s, "on", "a" ) | |
| 84 | } | |
| 85 | else if( pl.suffix( "[csx]is", s ) ) { | |
| 86 | # 5. Change -cis, -sis, -xis to -es | |
| 87 | result <- pl.inflect( s, "is", "es" ) | |
| 88 | } | |
| 89 | else if( pl.suffix( "([cs]h|ss)", s ) ) { | |
| 90 | # 8. Change -ch, -sh, -ss to -es | |
| 91 | result <- pl.inflect( s, "", "es" ) | |
| 92 | } | |
| 93 | else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) { | |
| 94 | # 9. Change -f to -ves | |
| 95 | result <- pl.inflect( s, "f", "ves" ) | |
| 96 | } | |
| 97 | else if( pl.suffix( "[nlw]ife", s ) ) { | |
| 98 | # 9. Change -fe to -ves | |
| 99 | result <- pl.inflect( s, "fe", "ves" ) | |
| 100 | } | |
| 101 | else if( pl.suffix( "([aeiou]y|[A-Z].*y)", s ) ) { | |
| 102 | # 10. Change -y to -ys. | |
| 103 | result <- pl.inflect( s, "", "s" ) | |
| 104 | } | |
| 105 | else if( pl.suffix( "y", s ) ) { | |
| 106 | # 10. Change -y to -ies. | |
| 107 | result <- pl.inflect( s, "y", "ies" ) | |
| 108 | } | |
| 109 | else { | |
| 110 | # 13. Default plural: add -s. | |
| 111 | result <- pl.inflect( s, "", "s" ) | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | result | |
| 116 | } | |
| 117 | ||
| 118 | # Pluralize s if n is not equal to 1. | |
| 119 | pl <- function( s, n ) { | |
| 120 | pluralize( s, x( n ) ) | |
| 121 | } | |
| 122 | ||
| 123 | # Returns the given string (s) with its suffix replaced by r. | |
| 124 | pl.inflect <- function( s, suffix, r ) { | |
| 125 | gsub( paste( suffix, "$", sep="" ), r, s ) | |
| 126 | } | |
| 127 | ||
| 128 | # Answers whether the given string (s) has the given ending. | |
| 129 | pl.suffix <- function( ending, s ) { | |
| 130 | grepl( paste( ending, "$", sep="" ), s ) | |
| 131 | } | |
| 132 | ||
| 133 | # Answers whether the given string (s) is a noninflective noun. | |
| 134 | pl.noninflective <- function( s ) { | |
| 135 | v <- c( | |
| 136 | "aircraft", "Bhutanese", "bison", "bream", "breeches", "britches", | |
| 137 | "Burmese", "carp", "chassis", "Chinese", "clippers", "cod", "contretemps", | |
| 138 | "corps", "debris", "diabetes", "djinn", "eland", "elk", "flounder", | |
| 139 | "fracas", "gallows", "graffiti", "headquarters", "herpes", "high-jinks", | |
| 140 | "homework", "hovercraft", "innings", "jackanapes", "Japanese", | |
| 141 | "Lebanese", "mackerel", "means", "measles", "mews", "mumps", "news", | |
| 142 | "pincers", "pliers", "Portuguese", "proceedings", "rabies", "salmon", | |
| 143 | "scissors", "sea-bass", "Senegalese", "series", "shears", "Siamese", | |
| 144 | "Sinhalese", "spacecraft", "species", "swine", "trout", "tuna", | |
| 145 | "Vietnamese", "watercraft", "whiting", "wildebeest" | |
| 146 | ) | |
| 147 | ||
| 148 | is.element( s, v ) | |
| 149 | } | |
| 150 | ||
| 151 | # Answers whether the given string (s) is an irregular plural. | |
| 152 | pl.is.irregular.pl <- function( s ) { | |
| 153 | # Could be refactored with pl.irregular.pl... | |
| 154 | v <- c( | |
| 155 | "beef", "brother", "child", "cow", "ephemeris", "genie", "money", | |
| 156 | "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby" | |
| 157 | ) | |
| 158 | ||
| 159 | is.element( s, v ) | |
| 160 | } | |
| 161 | ||
| 162 | # Call to pluralize an irregular noun. Only call after confirming | |
| 163 | # the noun is irregular via pl.is.irregular.pl. | |
| 164 | pl.irregular.pl <- function( s ) { | |
| 165 | v <- list( | |
| 166 | "beef" = "beefs", | |
| 167 | "brother" = "brothers", | |
| 168 | "child" = "children", | |
| 169 | "cow" = "cows", | |
| 170 | "ephemeris" = "ephemerides", | |
| 171 | "genie" = "genies", | |
| 172 | "money" = "moneys", | |
| 173 | "mongoose" = "mongooses", | |
| 174 | "mythos" = "mythoi", | |
| 175 | "octopus" = "octopuses", | |
| 176 | "ox" = "oxen", | |
| 177 | "soliloquy" = "soliloquies", | |
| 178 | "trilby" = "trilbys" | |
| 179 | ) | |
| 180 | ||
| 181 | # Faster version of v[[ s ]] | |
| 182 | .subset2( v, s ) | |
| 183 | } | |
| 184 | ||
| 185 | # Answers whether the given string (s) pluralizes with -es. | |
| 186 | pl.is.irregular.es <- function( s ) { | |
| 187 | v <- c( | |
| 188 | "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis", | |
| 189 | "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais", | |
| 190 | "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris", | |
| 191 | "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis", | |
| 192 | "polis", "rhinoceros", "sassafrass", "trellis" | |
| 193 | ) | |
| 194 | ||
| 195 | is.element( s, v ) | |
| 196 | } | |
| 197 | ||
| 1 | 198 |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultOptions |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultSettings |
| 1 | ||
| 1 | com.scrivenvar.service.impl.DefaultSnitch |
| 1 | ||
| 1 | com.scrivenvar.service.events.impl.DefaultNotifier |
| 1 | app.properties | |
| 1 | 2 |
| 1 | #!/bin/bash | |
| 2 | ||
| 3 | INKSCAPE="/usr/bin/inkscape" | |
| 4 | PNG_COMPRESS="optipng" | |
| 5 | PNG_COMPRESS_OPTS="-o9 *png" | |
| 6 | ICO_TOOL="icotool" | |
| 7 | ICO_TOOL_OPTS="-c -o ../../../../../icons/logo.ico logo64.png" | |
| 8 | ||
| 9 | declare -a SIZES=("16" "32" "64" "128" "256" "512") | |
| 10 | ||
| 11 | for i in "${SIZES[@]}"; do | |
| 12 | # -y: export background opacity 0 | |
| 13 | $INKSCAPE -y 0 -z -f "logo.svg" -w "${i}" -e "logo${i}.png" | |
| 14 | done | |
| 15 | ||
| 16 | # Compess the PNG images. | |
| 17 | which $PNG_COMPRESS && $PNG_COMPRESS $PNG_COMPRESS_OPTS | |
| 18 | ||
| 19 | # Generate an ICO file. | |
| 20 | which $ICO_TOOL && $ICO_TOOL $ICO_TOOL_OPTS | |
| 21 | ||
| 1 | 22 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | ||
| 29 | .markdown-editor { | |
| 30 | -fx-font-size: 11pt; | |
| 31 | } | |
| 32 | ||
| 33 | /* Subtly highlight the current paragraph. */ | |
| 34 | .markdown-editor .paragraph-box:has-caret { | |
| 35 | -fx-background-color: #fcfeff; | |
| 36 | } | |
| 37 | ||
| 38 | /* Light colour for selection highlight. */ | |
| 39 | .markdown-editor .selection { | |
| 40 | -fx-fill: #a6d2ff; | |
| 41 | } | |
| 42 | ||
| 43 | /* Decoration for words not found in the lexicon. */ | |
| 44 | .markdown-editor .spelling { | |
| 45 | -rtfx-underline-color: rgba(255, 131, 67, .7); | |
| 46 | -rtfx-underline-dash-array: 4, 2; | |
| 47 | -rtfx-underline-width: 2; | |
| 48 | -rtfx-underline-cap: round; | |
| 49 | } | |
| 1 | 50 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | |
| 3 | ||
| 4 | <svg | |
| 5 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 6 | xmlns:cc="http://creativecommons.org/ns#" | |
| 7 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 8 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 9 | xmlns="http://www.w3.org/2000/svg" | |
| 10 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 11 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 12 | id="svg2" | |
| 13 | version="1.1" | |
| 14 | inkscape:version="0.91 r13725" | |
| 15 | width="512" | |
| 16 | height="512" | |
| 17 | viewBox="0 0 512 512" | |
| 18 | sodipodi:docname="logo.svg"> | |
| 19 | <metadata | |
| 20 | id="metadata8"> | |
| 21 | <rdf:RDF> | |
| 22 | <cc:Work | |
| 23 | rdf:about=""> | |
| 24 | <dc:format>image/svg+xml</dc:format> | |
| 25 | <dc:type | |
| 26 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 27 | <dc:title></dc:title> | |
| 28 | </cc:Work> | |
| 29 | </rdf:RDF> | |
| 30 | </metadata> | |
| 31 | <defs | |
| 32 | id="defs6" /> | |
| 33 | <sodipodi:namedview | |
| 34 | pagecolor="#ffffff" | |
| 35 | bordercolor="#666666" | |
| 36 | borderopacity="1" | |
| 37 | objecttolerance="10" | |
| 38 | gridtolerance="10" | |
| 39 | guidetolerance="10" | |
| 40 | inkscape:pageopacity="0" | |
| 41 | inkscape:pageshadow="2" | |
| 42 | inkscape:window-width="640" | |
| 43 | inkscape:window-height="480" | |
| 44 | id="namedview4" | |
| 45 | showgrid="false" | |
| 46 | fit-margin-top="0" | |
| 47 | fit-margin-left="0" | |
| 48 | fit-margin-right="0" | |
| 49 | fit-margin-bottom="0" | |
| 50 | inkscape:zoom="1.2682274" | |
| 51 | inkscape:cx="15.646213" | |
| 52 | inkscape:cy="213.34955" | |
| 53 | inkscape:current-layer="svg2" /> | |
| 54 | <path | |
| 55 | style="fill:#ce6200;fill-opacity:1" | |
| 56 | d="m 203.2244,511.85078 c -60.01827,-1.2968 -121.688643,-6.5314 -192.436493,-16.334 -5.8078027,-0.8047 -10.66110747,-1.561 -10.78511762,-1.6806 -0.12404567,-0.1196 3.90488112,-4.5812 8.95313512,-9.9147 32.9484785,-34.8102 70.4314485,-73.8923 104.1521555,-108.5956 l 11.87611,-12.2221 5.48905,-10.2177 c 35.82801,-66.6927 75.13064,-128.5665 105.90637,-166.7277 6.13805,-7.611 10.21451,-12.0689 17.28719,-18.9048 36.6818,-35.4537 108.27279,-83.724003 206.0323,-138.917303 22.10365,-12.47935 51.93386,-28.64995037 52.26391,-28.33165037 0.38883,0.37499 -2.35932,25.95575037 -4.86585,45.29275037 -7.28943,56.236403 -17.04619,103.128903 -28.07642,134.939803 -7.19617,20.7536 -14.81287,35.152 -22.9667,43.4155 -3.60444,3.6529 -6.58328,5.7941 -10.1313,7.2825 l -2.56414,1.0756 -53.43164,0.1713 -53.43166,0.1713 3.69973,1.8547 c 26.78565,13.4282 52.58051,27.5241 59.57122,32.5533 4.48397,3.2259 4.41278,2.9854 1.59124,5.3784 -26.99514,22.8955 -74.52961,44.0013 -140.23089,62.2641 -26.34995,7.3244 -57.85469,14.6842 -86.99871,20.3237 l -10.26943,1.9871 -52.01052,53.2733 -52.010524,53.2732 -29.459801,15.1165 c -26.4100885,13.5517 -29.3446639,15.1388 -28.347645,15.3311 0.6117029,0.118 4.0894221,0.2188 7.7282726,0.2239 3.6388854,0.01 16.1273694,0.2329 27.7522124,0.5059 51.576376,1.2116 146.083985,1.512 170.154295,0.5409 34.66996,-1.3988 52.7606,-2.9325 67.58258,-5.7293 2.68664,-0.507 4.82907,-0.9755 4.76094,-1.0412 -0.0681,-0.066 -3.24733,-0.8833 -7.0649,-1.8169 -8.04133,-1.9664 -25.10167,-5.3107 -41.1231,-8.0612 -47.6405,-8.1787 -65.48708,-12.0107 -74.13028,-15.9169 -3.90548,-1.7651 -7.13816,-4.7659 -8.12937,-7.5463 -1.01822,-2.8562 -0.92214,-6.5271 0.23315,-8.9083 1.86563,-3.8451 6.14837,-6.7199 12.26745,-8.2345 16.96993,-4.2004 57.27977,-6.1832 90.36228,-4.4448 54.7332,2.8761 117.0767,13.1228 178.50212,29.3385 18.03514,4.7611 51.66065,14.656 51.22677,15.0744 -0.0824,0.08 -5.72762,-0.854 -12.54488,-2.0745 -40.1043,-7.18 -60.50854,-10.2888 -101.40822,-15.4507 -24.4851,-3.0902 -55.12614,-5.9915 -77.58876,-7.3465 -26.58826,-1.6039 -61.15821,-1.7754 -80.99202,-0.4019 l -3.19705,0.2214 8.70308,1.4934 c 51.89698,8.9047 77.51746,14.9877 88.00479,20.8948 6.9134,3.894 10.30497,9.4381 9.33333,15.2569 -1.50397,9.0066 -10.51381,14.0257 -32.00273,17.8278 -16.31374,2.8863 -47.27575,4.3845 -77.23553,3.7371 z" | |
| 57 | id="path4138" /> | |
| 58 | <path | |
| 59 | style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" | |
| 60 | d="m 214.76931,324.51908 c 60.83777,-14.1145 111.89562,-31.6251 144.40025,-49.5229 3.12602,-1.7213 5.81747,-3.2537 5.98106,-3.4054 0.40534,-0.3759 -13.76388,-7.9415 -34.63489,-18.4929 -7.52161,-3.8026 -9.82337,-5.3787 -12.0735,-8.2668 -5.14485,-6.6036 -5.96081,-14.8404 -2.20331,-22.2417 1.80288,-3.5512 5.69484,-7.3007 9.36158,-9.019 5.20851,-2.4407 1.18148,-2.2865 59.71223,-2.2865 l 52.81361,0 2.13233,-2.1984 c 2.78673,-2.8731 5.23414,-6.4981 8.23035,-12.1905 14.14966,-26.8827 26.71842,-78.3816 36.24347,-148.503303 0.76704,-5.6468 1.36194,-10.2983 1.32201,-10.3369 -0.0399,-0.038 -5.47754,2.9629 -12.08361,6.6697 l -12.01104,6.7396 -133.83068,137.037303 c -73.60688,75.3705 -134.81732,138.0567 -136.0232,139.3026 l -2.19251,2.2653 8.254,-1.8067 c 4.53969,-0.9937 12.01053,-2.6783 16.60185,-3.7435 z" | |
| 61 | id="path4136" /> | |
| 62 | <path | |
| 63 | style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1" | |
| 64 | d="m 202.72524,284.43588 c 69.93294,-70.1332 135.4799,-131.9279 213.46406,-201.244203 7.71421,-6.8568 14.50542,-12.9341 15.09155,-13.5052 0.9482,-0.9239 0.96778,-0.9811 0.17761,-0.5188 -77.96496,45.611803 -139.23519,88.710503 -166.72539,117.278203 -18.81811,19.5556 -50.35654,64.861 -80.96704,116.3104 -0.91787,1.5427 1.02249,-0.3323 18.95921,-18.3204 z" | |
| 65 | id="path4142" /> | |
| 66 | <path | |
| 67 | style="fill:#000000" | |
| 68 | d="" | |
| 69 | id="path4140" | |
| 70 | inkscape:connector-curvature="0" /> | |
| 71 | </svg> | |
| 1 | 72 |
| 1 | # ######################################################################## | |
| 2 | # Main Application Window | |
| 3 | # ######################################################################## | |
| 4 | ||
| 5 | # suppress inspection "UnusedProperty" for whole file | |
| 6 | ||
| 7 | # The application title should exist only once in the entire code base. | |
| 8 | # All other references should either refer to this value via the Messages | |
| 9 | # class, or indirectly using ${Main.title}. | |
| 10 | Main.title=Scrivenvar | |
| 11 | ||
| 12 | Main.menu.file=_File | |
| 13 | Main.menu.file.new=_New | |
| 14 | Main.menu.file.open=_Open... | |
| 15 | Main.menu.file.close=_Close | |
| 16 | Main.menu.file.close_all=Close All | |
| 17 | Main.menu.file.save=_Save | |
| 18 | Main.menu.file.save_as=Save _As | |
| 19 | Main.menu.file.save_all=Save A_ll | |
| 20 | Main.menu.file.exit=E_xit | |
| 21 | ||
| 22 | Main.menu.edit=_Edit | |
| 23 | Main.menu.edit.copy.html=Copy _HTML | |
| 24 | Main.menu.edit.undo=_Undo | |
| 25 | Main.menu.edit.redo=_Redo | |
| 26 | Main.menu.edit.cut=Cu_t | |
| 27 | Main.menu.edit.copy=_Copy | |
| 28 | Main.menu.edit.paste=_Paste | |
| 29 | Main.menu.edit.selectAll=Select _All | |
| 30 | Main.menu.edit.find=_Find | |
| 31 | Main.menu.edit.find.next=Find _Next | |
| 32 | Main.menu.edit.preferences=_Preferences | |
| 33 | ||
| 34 | Main.menu.insert=_Insert | |
| 35 | Main.menu.insert.blockquote=_Blockquote | |
| 36 | Main.menu.insert.code=Inline _Code | |
| 37 | Main.menu.insert.fenced_code_block=_Fenced Code Block | |
| 38 | Main.menu.insert.fenced_code_block.prompt=Enter code here | |
| 39 | Main.menu.insert.link=_Link... | |
| 40 | Main.menu.insert.image=_Image... | |
| 41 | Main.menu.insert.heading.1=Heading _1 | |
| 42 | Main.menu.insert.heading.1.prompt=heading 1 | |
| 43 | Main.menu.insert.heading.2=Heading _2 | |
| 44 | Main.menu.insert.heading.2.prompt=heading 2 | |
| 45 | Main.menu.insert.heading.3=Heading _3 | |
| 46 | Main.menu.insert.heading.3.prompt=heading 3 | |
| 47 | Main.menu.insert.unordered_list=_Unordered List | |
| 48 | Main.menu.insert.ordered_list=_Ordered List | |
| 49 | Main.menu.insert.horizontal_rule=_Horizontal Rule | |
| 50 | ||
| 51 | Main.menu.format=Forma_t | |
| 52 | Main.menu.format.bold=_Bold | |
| 53 | Main.menu.format.italic=_Italic | |
| 54 | Main.menu.format.superscript=Su_perscript | |
| 55 | Main.menu.format.subscript=Su_bscript | |
| 56 | Main.menu.format.strikethrough=Stri_kethrough | |
| 57 | ||
| 58 | Main.menu.definition=_Definition | |
| 59 | Main.menu.definition.create=_Create | |
| 60 | Main.menu.definition.insert=_Insert | |
| 61 | ||
| 62 | Main.menu.help=_Help | |
| 63 | Main.menu.help.about=About ${Main.title} | |
| 64 | ||
| 65 | # ######################################################################## | |
| 66 | # Status Bar | |
| 67 | # ######################################################################## | |
| 68 | ||
| 69 | Main.status.text.offset=offset | |
| 70 | Main.status.line=Line {0} of {1}, ${Main.status.text.offset} {2} | |
| 71 | Main.status.state.default=OK | |
| 72 | Main.status.error.parse={0} (near ${Main.status.text.offset} {1}) | |
| 73 | Main.status.error.def.blank=Move the caret to a word before inserting a definition. | |
| 74 | Main.status.error.def.empty=Create a definition before inserting a definition. | |
| 75 | Main.status.error.def.missing=No definition value found for ''{0}''. | |
| 76 | Main.status.error.r=Error with [{0}...]: {1} | |
| 77 | ||
| 78 | # ######################################################################## | |
| 79 | # Preferences | |
| 80 | # ######################################################################## | |
| 81 | ||
| 82 | Preferences.r=R | |
| 83 | Preferences.r.script=Startup Script | |
| 84 | Preferences.r.script.desc=Script runs prior to executing R statements within the document. | |
| 85 | Preferences.r.directory=Working Directory | |
| 86 | Preferences.r.directory.desc=Value assigned to $application.r.working.directory$ and usable in the startup script. | |
| 87 | Preferences.r.delimiter.began=Delimiter Prefix | |
| 88 | Preferences.r.delimiter.began.desc=Prefix of expression that wraps inserted definitions. | |
| 89 | Preferences.r.delimiter.ended=Delimiter Suffix | |
| 90 | Preferences.r.delimiter.ended.desc=Suffix of expression that wraps inserted definitions. | |
| 91 | ||
| 92 | Preferences.images=Images | |
| 93 | Preferences.images.directory=Relative Directory | |
| 94 | Preferences.images.directory.desc=Path prepended to embedded images referenced using local file paths. | |
| 95 | Preferences.images.suffixes=Extensions | |
| 96 | Preferences.images.suffixes.desc=Preferred order of image file types to embed, separated by spaces. | |
| 97 | ||
| 98 | Preferences.definitions=Definitions | |
| 99 | Preferences.definitions.path=File name | |
| 100 | Preferences.definitions.path.desc=Absolute path to interpolated string definitions. | |
| 101 | Preferences.definitions.delimiter.began=Delimiter Prefix | |
| 102 | Preferences.definitions.delimiter.began.desc=Indicates when a definition key is starting. | |
| 103 | Preferences.definitions.delimiter.ended=Delimiter Suffix | |
| 104 | Preferences.definitions.delimiter.ended.desc=Indicates when a definition key is ending. | |
| 105 | ||
| 106 | Preferences.fonts=Editor | |
| 107 | Preferences.fonts.size_editor=Font Size | |
| 108 | Preferences.fonts.size_editor.desc=Font size to use for the text editor. | |
| 109 | ||
| 110 | # ######################################################################## | |
| 111 | # Definition Pane and its Tree View | |
| 112 | # ######################################################################## | |
| 113 | ||
| 114 | Definition.menu.create=Create | |
| 115 | Definition.menu.rename=Rename | |
| 116 | Definition.menu.remove=Delete | |
| 117 | Definition.menu.add.default=Undefined | |
| 118 | ||
| 119 | # ######################################################################## | |
| 120 | # Failure messages with respect to YAML files. | |
| 121 | # ######################################################################## | |
| 122 | yaml.error.open=Could not open YAML file (ensure non-empty file). | |
| 123 | yaml.error.unresolvable=Too much indirection for: ''{0}'' = ''{1}''. | |
| 124 | yaml.error.missing=Empty definition value for key ''{0}''. | |
| 125 | yaml.error.tree.form=Unassigned definition near ''{0}''. | |
| 126 | ||
| 127 | # ######################################################################## | |
| 128 | # File Editor | |
| 129 | # ######################################################################## | |
| 130 | ||
| 131 | FileEditor.loadFailed.message=Failed to load ''{0}''.\n\nReason: {1} | |
| 132 | FileEditor.loadFailed.title=Load | |
| 133 | FileEditor.loadFailed.reason.permissions=File must be readable and writable. | |
| 134 | FileEditor.saveFailed.message=Failed to save ''{0}''.\n\nReason: {1} | |
| 135 | FileEditor.saveFailed.title=Save | |
| 136 | ||
| 137 | # ######################################################################## | |
| 138 | # File Open | |
| 139 | # ######################################################################## | |
| 140 | ||
| 141 | Dialog.file.choose.open.title=Open File | |
| 142 | Dialog.file.choose.save.title=Save File | |
| 143 | ||
| 144 | Dialog.file.choose.filter.title.source=Source Files | |
| 145 | Dialog.file.choose.filter.title.definition=Definition Files | |
| 146 | Dialog.file.choose.filter.title.xml=XML Files | |
| 147 | Dialog.file.choose.filter.title.all=All Files | |
| 148 | ||
| 149 | # ######################################################################## | |
| 150 | # Alert Dialog | |
| 151 | # ######################################################################## | |
| 152 | ||
| 153 | Alert.file.close.title=Close | |
| 154 | Alert.file.close.text=Save changes to {0}? | |
| 155 | ||
| 156 | # ######################################################################## | |
| 157 | # Definition Pane | |
| 158 | # ######################################################################## | |
| 159 | ||
| 160 | Pane.definition.node.root.title=Definitions | |
| 161 | Pane.definition.button.create.label=_Create | |
| 162 | Pane.definition.button.rename.label=_Rename | |
| 163 | Pane.definition.button.delete.label=_Delete | |
| 164 | Pane.definition.button.create.tooltip=Add new item (Insert) | |
| 165 | Pane.definition.button.rename.tooltip=Rename selected item (F2) | |
| 166 | Pane.definition.button.delete.tooltip=Delete selected items (Delete) | |
| 167 | ||
| 168 | # Controls ############################################################### | |
| 169 | ||
| 170 | # ######################################################################## | |
| 171 | # Browse File | |
| 172 | # ######################################################################## | |
| 173 | ||
| 174 | BrowseFileButton.chooser.title=Browse for local file | |
| 175 | BrowseFileButton.chooser.allFilesFilter=All Files | |
| 176 | BrowseFileButton.tooltip=${BrowseFileButton.chooser.title} | |
| 177 | ||
| 178 | # Dialogs ################################################################ | |
| 179 | ||
| 180 | # ######################################################################## | |
| 181 | # Image | |
| 182 | # ######################################################################## | |
| 183 | ||
| 184 | Dialog.image.title=Image | |
| 185 | Dialog.image.chooser.imagesFilter=Images | |
| 186 | Dialog.image.previewLabel.text=Markdown Preview\: | |
| 187 | Dialog.image.textLabel.text=Alternate Text\: | |
| 188 | Dialog.image.titleLabel.text=Title (tooltip)\: | |
| 189 | Dialog.image.urlLabel.text=Image URL\: | |
| 190 | ||
| 191 | # ######################################################################## | |
| 192 | # Hyperlink | |
| 193 | # ######################################################################## | |
| 194 | ||
| 195 | Dialog.link.title=Link | |
| 196 | Dialog.link.previewLabel.text=Markdown Preview\: | |
| 197 | Dialog.link.textLabel.text=Link Text\: | |
| 198 | Dialog.link.titleLabel.text=Title (tooltip)\: | |
| 199 | Dialog.link.urlLabel.text=Link URL\: | |
| 200 | ||
| 201 | # ######################################################################## | |
| 202 | # About | |
| 203 | # ######################################################################## | |
| 204 | ||
| 205 | Dialog.about.title=About | |
| 206 | Dialog.about.header=${Main.title} | |
| 207 | Dialog.about.content=Copyright 2020 White Magic Software, Ltd.\n\nBased on Markdown Writer FX by Karl Tauber | |
| 1 | 208 |
| 1 | /* RESET ***/ | |
| 2 | html{box-sizing:border-box;font-size:12pt}body,h1,h2,h3,h4,h5,h6,ol,p,ul{margin:0;padding:0}img{max-width:100%;height:auto}table{table-collapse:collapse;table-spacing:0;border-spacing:0} | |
| 3 | ||
| 4 | /* BODY ***/ | |
| 5 | body { | |
| 6 | /* Must be bundled in JAR file. */ | |
| 7 | font-family: "Vollkorn", serif; | |
| 8 | background-color: #fff; | |
| 9 | margin: 0 auto; | |
| 10 | max-width: 960px; | |
| 11 | line-height: 1.6; | |
| 12 | color: #454545; | |
| 13 | padding: 0 1em; | |
| 14 | font-feature-settings: "liga" 1; | |
| 15 | font-variant-ligatures: normal; | |
| 16 | } | |
| 17 | ||
| 18 | body>*:first-child { | |
| 19 | margin-top: 0 !important; | |
| 20 | } | |
| 21 | ||
| 22 | body>*:last-child { | |
| 23 | margin-bottom: 0 !important; | |
| 24 | } | |
| 25 | ||
| 26 | /* BLOCKS ***/ | |
| 27 | p, blockquote, ul, ol, dl, table, pre { | |
| 28 | margin: 1em 0; | |
| 29 | vertical-align: middle; | |
| 30 | } | |
| 31 | ||
| 32 | /* HEADINGS ***/ | |
| 33 | h1, h2, h3, h4, h5, h6 { | |
| 34 | font-weight: bold; | |
| 35 | margin: 1em 0 .5em; | |
| 36 | } | |
| 37 | ||
| 38 | h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, | |
| 39 | h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { | |
| 40 | font-size: inherit; | |
| 41 | } | |
| 42 | ||
| 43 | h1 { | |
| 44 | font-size: 21pt; | |
| 45 | } | |
| 46 | ||
| 47 | h2 { | |
| 48 | font-size: 18pt; | |
| 49 | border-bottom: 1px solid #ccc; | |
| 50 | } | |
| 51 | ||
| 52 | h3 { | |
| 53 | font-size: 15pt; | |
| 54 | } | |
| 55 | ||
| 56 | h4 { | |
| 57 | font-size: 13.5pt; | |
| 58 | } | |
| 59 | ||
| 60 | h5 { | |
| 61 | font-size: 12pt; | |
| 62 | } | |
| 63 | ||
| 64 | h6 { | |
| 65 | font-size: 10.5pt; | |
| 66 | } | |
| 67 | ||
| 68 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { | |
| 69 | margin-top: .5em; | |
| 70 | } | |
| 71 | ||
| 72 | /* LINKS ***/ | |
| 73 | a { | |
| 74 | color: #0077aa; | |
| 75 | text-decoration: none; | |
| 76 | } | |
| 77 | ||
| 78 | a:hover { | |
| 79 | text-decoration: underline; | |
| 80 | } | |
| 81 | ||
| 82 | /* BULLET LISTS ***/ | |
| 83 | ul, ol { | |
| 84 | display: block; | |
| 85 | list-style: disc outside none; | |
| 86 | margin: 1em 0; | |
| 87 | padding: 0 0 0 2em; | |
| 88 | } | |
| 89 | ||
| 90 | ol { | |
| 91 | list-style-type: decimal; | |
| 92 | } | |
| 93 | ||
| 94 | ul ul, ol ul, | |
| 95 | ol ol, ul ol { | |
| 96 | list-style-position: inside; | |
| 97 | margin-left: 1em; | |
| 98 | } | |
| 99 | ||
| 100 | ul ul, ol ul { | |
| 101 | list-style-type: circle; | |
| 102 | } | |
| 103 | ||
| 104 | ol ol, ul ol { | |
| 105 | list-style-type: lower-latin; | |
| 106 | } | |
| 107 | ||
| 108 | /* DEFINITION LISTS ***/ | |
| 109 | dl { | |
| 110 | /** Horizontal scroll bar will appear if set to 100%. */ | |
| 111 | width: 99%; | |
| 112 | overflow: hidden; | |
| 113 | padding-left: 1em; | |
| 114 | } | |
| 115 | ||
| 116 | dl dt { | |
| 117 | font-weight: bold; | |
| 118 | float: left; | |
| 119 | width: 20%; | |
| 120 | clear: both; | |
| 121 | position: relative; | |
| 122 | } | |
| 123 | ||
| 124 | dl dd { | |
| 125 | float: right; | |
| 126 | width: 79%; | |
| 127 | padding-bottom: .5em; | |
| 128 | margin-left: 0; | |
| 129 | } | |
| 130 | ||
| 131 | /* CODE ***/ | |
| 132 | pre, code, tt { | |
| 133 | /* Must be bundled in JAR file. */ | |
| 134 | font-family: "Fira Code", monospace; | |
| 135 | font-size: 10pt; | |
| 136 | background-color: #f8f8f8; | |
| 137 | text-decoration: none; | |
| 138 | white-space: pre-wrap; | |
| 139 | word-wrap: break-word; | |
| 140 | overflow-wrap: anywhere; | |
| 141 | border-radius: .125em; | |
| 142 | } | |
| 143 | ||
| 144 | code, tt { | |
| 145 | padding: .25em; | |
| 146 | } | |
| 147 | ||
| 148 | pre > code { | |
| 149 | /* Reset the padding. */ | |
| 150 | padding: 0; | |
| 151 | border: none; | |
| 152 | background: transparent; | |
| 153 | } | |
| 154 | ||
| 155 | pre { | |
| 156 | border: .125em solid #ccc; | |
| 157 | overflow: auto; | |
| 158 | /* Assign the new padding, independently from previous. */ | |
| 159 | padding: .25em .5em; | |
| 160 | } | |
| 161 | ||
| 162 | pre code, pre tt { | |
| 163 | background-color: transparent; | |
| 164 | border: none; | |
| 165 | } | |
| 166 | ||
| 167 | /* QUOTES ***/ | |
| 168 | blockquote { | |
| 169 | border-left: .25em solid #ccc; | |
| 170 | padding: 0 1em; | |
| 171 | color: #777; | |
| 172 | } | |
| 173 | ||
| 174 | blockquote>:first-child { | |
| 175 | margin-top: 0; | |
| 176 | } | |
| 177 | ||
| 178 | blockquote>:last-child { | |
| 179 | margin-bottom: 0; | |
| 180 | } | |
| 181 | ||
| 182 | /* HORIZONTAL RULES ***/ | |
| 183 | hr { | |
| 184 | clear: both; | |
| 185 | margin: 1.5em 0 1.5em; | |
| 186 | height: 0; | |
| 187 | overflow: hidden; | |
| 188 | border: none; | |
| 189 | background: transparent; | |
| 190 | border-bottom: .125em solid #ccc; | |
| 191 | } | |
| 192 | ||
| 193 | /* TABLES ***/ | |
| 194 | table { | |
| 195 | width: 100%; | |
| 196 | } | |
| 197 | ||
| 198 | tr:nth-child(odd) { | |
| 199 | background-color: #eee; | |
| 200 | } | |
| 201 | ||
| 202 | th { | |
| 203 | background-color: #454545; | |
| 204 | color: #fff; | |
| 205 | } | |
| 206 | ||
| 207 | th, td { | |
| 208 | text-align: left; | |
| 209 | padding: 0 1em; | |
| 210 | } | |
| 211 | ||
| 212 | /* IMAGES ***/ | |
| 213 | img { | |
| 214 | max-width: 100%; | |
| 215 | } | |
| 216 | ||
| 217 | /* Required for FlyingSaucer to detect the node. | |
| 218 | * See SVGReplacedElementFactory for details. | |
| 219 | */ | |
| 220 | svg, tex { | |
| 221 | /* Ensure the formulas can be inlined with text. */ | |
| 222 | display: inline-block; | |
| 223 | vertical-align: middle; | |
| 224 | } | |
| 1 | 225 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | /*---- toolbar ----*/ | |
| 29 | ||
| 30 | .tool-bar { | |
| 31 | -fx-spacing: 0; | |
| 32 | } | |
| 33 | ||
| 34 | .tool-bar .button { | |
| 35 | -fx-background-color: transparent; | |
| 36 | } | |
| 37 | ||
| 38 | .tool-bar .button:hover { | |
| 39 | -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color; | |
| 40 | -fx-color: -fx-hover-base; | |
| 41 | } | |
| 42 | ||
| 43 | .tool-bar .button:armed { | |
| 44 | -fx-color: -fx-pressed-base; | |
| 45 | } | |
| 1 | 46 |
| 1 | # ######################################################################## | |
| 2 | # Application | |
| 3 | # ######################################################################## | |
| 4 | ||
| 5 | application.title=scrivenvar | |
| 6 | application.package=com/${application.title} | |
| 7 | application.messages= com.${application.title}.messages | |
| 8 | ||
| 9 | # Suppress multiple file modified notifications for one logical modification. | |
| 10 | # Given in milliseconds. | |
| 11 | application.watchdog.timeout=50 | |
| 12 | ||
| 13 | # ######################################################################## | |
| 14 | # Preferences | |
| 15 | # ######################################################################## | |
| 16 | ||
| 17 | preferences.root=com.${application.title} | |
| 18 | preferences.root.state=state | |
| 19 | preferences.root.options=options | |
| 20 | preferences.root.definition.source=definition.source | |
| 21 | ||
| 22 | # ######################################################################## | |
| 23 | # File and Path References | |
| 24 | # ######################################################################## | |
| 25 | file.stylesheet.scene=${application.package}/scene.css | |
| 26 | file.stylesheet.markdown=${application.package}/editor/markdown.css | |
| 27 | file.stylesheet.preview=webview.css | |
| 28 | file.stylesheet.xml=${application.package}/xml.css | |
| 29 | ||
| 30 | file.logo.16 =${application.package}/logo16.png | |
| 31 | file.logo.32 =${application.package}/logo32.png | |
| 32 | file.logo.128=${application.package}/logo128.png | |
| 33 | file.logo.256=${application.package}/logo256.png | |
| 34 | file.logo.512=${application.package}/logo512.png | |
| 35 | ||
| 36 | # Default file name when a new file is created. | |
| 37 | # This ensures that the file type can always be | |
| 38 | # discerned so that the correct type of variable | |
| 39 | # reference can be inserted. | |
| 40 | file.default=untitled.md | |
| 41 | file.definition.default=variables.yaml | |
| 42 | ||
| 43 | # ######################################################################## | |
| 44 | # File name Extensions | |
| 45 | # ######################################################################## | |
| 46 | ||
| 47 | # Comma-separated list of definition file name extensions. | |
| 48 | definition.file.ext.json=*.json | |
| 49 | definition.file.ext.toml=*.toml | |
| 50 | definition.file.ext.yaml=*.yml,*.yaml | |
| 51 | definition.file.ext.properties=*.properties,*.props | |
| 52 | ||
| 53 | # Comma-separated list of file name extensions. | |
| 54 | file.ext.rmarkdown=*.Rmd | |
| 55 | file.ext.rxml=*.Rxml | |
| 56 | file.ext.source=*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt,${file.ext.rmarkdown},${file.ext.rxml} | |
| 57 | file.ext.definition=${definition.file.ext.yaml} | |
| 58 | file.ext.xml=*.xml,${file.ext.rxml} | |
| 59 | file.ext.all=*.* | |
| 60 | ||
| 61 | # File name extension search order for images. | |
| 62 | file.ext.image.order=svg pdf png jpg tiff | |
| 63 | ||
| 64 | # ######################################################################## | |
| 65 | # Variable Name Editor | |
| 66 | # ######################################################################## | |
| 67 | ||
| 68 | # Maximum number of characters for a variable name. A variable is defined | |
| 69 | # as one or more non-whitespace characters up to this maximum length. | |
| 70 | editor.variable.maxLength=256 | |
| 71 | ||
| 72 | # ######################################################################## | |
| 73 | # Dialog Preferences | |
| 74 | # ######################################################################## | |
| 75 | ||
| 76 | dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R | |
| 77 | dialog.alert.button.order.linux=L_HE+UNYACBXIO_R | |
| 78 | dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R | |
| 79 | ||
| 80 | # Ensures a consistent button order for alert dialogs across platforms (because | |
| 81 | # the default button order on Linux defies all logic). | |
| 82 | dialog.alert.button.order=${dialog.alert.button.order.windows} | |
| 1 | 83 |
| 1 | --- | |
| 2 | c: | |
| 3 | protagonist: | |
| 4 | name: | |
| 5 | First: Chloe | |
| 6 | First_pos: $c.protagonist.name.First$'s | |
| 7 | Middle: Irene | |
| 8 | Family: Angelos | |
| 9 | nick: | |
| 10 | Father: Savant | |
| 11 | Mother: Sweetie | |
| 12 | colour: | |
| 13 | eyes: green | |
| 14 | hair: dark auburn | |
| 15 | syn_1: black | |
| 16 | syn_2: purple | |
| 17 | syn_11: teal | |
| 18 | syn_6: silver | |
| 19 | favourite: emerald green | |
| 20 | speech: | |
| 21 | tic: oh | |
| 22 | father: | |
| 23 | heritage: Greek | |
| 24 | name: | |
| 25 | Short: Bryce | |
| 26 | First: Bryson | |
| 27 | First_pos: $c.protagonist.father.name.First$'s | |
| 28 | Honourific: Mr. | |
| 29 | education: Masters | |
| 30 | vocation: | |
| 31 | name: robotics | |
| 32 | title: roboticist | |
| 33 | employer: | |
| 34 | name: | |
| 35 | Short: Rabota | |
| 36 | Full: $c.protagonist.father.employer.name.Short$ Designs | |
| 37 | hair: | |
| 38 | style: thick, curly | |
| 39 | colour: black | |
| 40 | eyes: | |
| 41 | colour: dark brown | |
| 42 | Endear: Dad | |
| 43 | vehicle: coupé | |
| 44 | mother: | |
| 45 | name: | |
| 46 | Short: Cass | |
| 47 | First: Cassandra | |
| 48 | First_pos: $c.protagonist.mother.name.First$'s | |
| 49 | Honourific: Mrs. | |
| 50 | education: PhD | |
| 51 | speech: | |
| 52 | tic: cute | |
| 53 | Honorific: Doctor | |
| 54 | vocation: | |
| 55 | article: an | |
| 56 | name: oceanography | |
| 57 | title: oceanographer | |
| 58 | employer: | |
| 59 | name: | |
| 60 | Full: Oregon State University | |
| 61 | Short: OSU | |
| 62 | eyes: | |
| 63 | colour: blue | |
| 64 | hair: | |
| 65 | style: thick, curly | |
| 66 | colour: dark brown | |
| 67 | Endear: Mom | |
| 68 | Endear_pos: Mom's | |
| 69 | uncle: | |
| 70 | name: | |
| 71 | First: Damian | |
| 72 | First_pos: $c.protagonist.uncle.name.First$'s | |
| 73 | Family: Moros | |
| 74 | hands: | |
| 75 | fingers: | |
| 76 | shape: long, bony | |
| 77 | friend: | |
| 78 | primary: | |
| 79 | name: | |
| 80 | First: Gerard | |
| 81 | First_pos: $c.protagonist.friend.primary.name.First$'s | |
| 82 | Family: Baran | |
| 83 | Family_pos: $c.protagonist.friend.primary.name.Family$'s | |
| 84 | favourite: | |
| 85 | colour: midnight blue | |
| 86 | eyes: | |
| 87 | colour: hazel | |
| 88 | mother: | |
| 89 | name: | |
| 90 | First: Isabella | |
| 91 | Short: Izzy | |
| 92 | Honourific: Mrs. | |
| 93 | father: | |
| 94 | name: | |
| 95 | Short: Mo | |
| 96 | First: Montgomery | |
| 97 | First_pos: $c.protagonist.friend.primary.father.name.First$'s | |
| 98 | Honourific: Mr. | |
| 99 | speech: | |
| 100 | tic: y'know | |
| 101 | endear: Pops | |
| 102 | military: | |
| 103 | primary: | |
| 104 | name: | |
| 105 | First: Felix | |
| 106 | Family: LeMay | |
| 107 | Family_pos: LeMay's | |
| 108 | rank: | |
| 109 | Short: General | |
| 110 | Full: Brigadier $c.military.primary.rank.Short$ | |
| 111 | colour: | |
| 112 | eyes: gray | |
| 113 | hair: dirty brown | |
| 114 | secondary: | |
| 115 | name: | |
| 116 | Family: Grell | |
| 117 | rank: Colonel | |
| 118 | colour: | |
| 119 | eyes: green | |
| 120 | hair: deep red | |
| 121 | quaternary: | |
| 122 | name: | |
| 123 | First: Gretchen | |
| 124 | Family: Steinherz | |
| 125 | minor: | |
| 126 | primary: | |
| 127 | name: | |
| 128 | First: River | |
| 129 | Family: Banks | |
| 130 | Honourific: Mx. | |
| 131 | vocation: | |
| 132 | title: salesperson | |
| 133 | employer: | |
| 134 | Name: Geophysical Prospecting Incorporated | |
| 135 | Abbr: GPI | |
| 136 | Area: Cold Spring Creek | |
| 137 | payment: twenty million | |
| 138 | secondary: | |
| 139 | name: | |
| 140 | First: Renato | |
| 141 | Middle: Carroña | |
| 142 | Family: Salvatierra | |
| 143 | Family_pos: $c.minor.secondary.name.Family$'s | |
| 144 | Full: $c.minor.secondary.name.First$ $c.minor.secondary.name.Middle$ Alejandro Gregorio Eduardo Salomón Vidal $c.minor.secondary.name.Family$ | |
| 145 | Honourific: Mister | |
| 146 | Honourific_sp: Señor | |
| 147 | vocation: | |
| 148 | title: detective | |
| 149 | tertiary: | |
| 150 | name: | |
| 151 | First: Robert | |
| 152 | Family: Hanssen | |
| 153 | ||
| 154 | ai: | |
| 155 | protagonist: | |
| 156 | name: | |
| 157 | first: yoky | |
| 158 | First: Yoky | |
| 159 | First_pos: $c.ai.protagonist.name.First$'s | |
| 160 | Family: Tsukuda | |
| 161 | id: 46692 | |
| 162 | persona: | |
| 163 | name: | |
| 164 | First: Hoshi | |
| 165 | First_pos: $c.ai.protagonist.persona.name.First$'s | |
| 166 | Family: Yamamoto | |
| 167 | Family_pos: $c.ai.protagonist.persona.name.Family$'s | |
| 168 | culture: Japanese-American | |
| 169 | ethnicity: Asian | |
| 170 | rank: Technical Sergeant | |
| 171 | speech: | |
| 172 | tic: okay | |
| 173 | first: | |
| 174 | Name: Prôtos | |
| 175 | Name_pos: Prôtos' | |
| 176 | age: | |
| 177 | actual: twenty-six weeks | |
| 178 | virtual: five years | |
| 179 | second: | |
| 180 | Name: Défteros | |
| 181 | third: | |
| 182 | Name: Trítos | |
| 183 | fourth: | |
| 184 | Name: Tétartos | |
| 185 | material: | |
| 186 | type: metal | |
| 187 | raw: ilmenite | |
| 188 | extract: ore | |
| 189 | name: | |
| 190 | short: titanium | |
| 191 | long: $c.ai.material.name.short$ dioxide | |
| 192 | Abbr: TiO~2~ | |
| 193 | pejorative: tin | |
| 194 | animal: | |
| 195 | protagonist: | |
| 196 | Name: Trufflers | |
| 197 | type: pig | |
| 198 | antagonist: | |
| 199 | name: coywolf | |
| 200 | Name: Coywolf | |
| 201 | plural: coywolves | |
| 202 | ||
| 203 | narrator: | |
| 204 | one: (by $c.protagonist.father.name.First$ $c.protagonist.name.Family$) | |
| 205 | two: (by $c.protagonist.mother.name.First$ $c.protagonist.name.Family$) | |
| 206 | ||
| 207 | military: | |
| 208 | name: | |
| 209 | Short: Agency | |
| 210 | Short_pos: $military.name.Short$'s | |
| 211 | plural: agencies | |
| 212 | machine: | |
| 213 | Name: Skopós | |
| 214 | Name_pos: $military.machine.Name$' | |
| 215 | Location: Arctic | |
| 216 | predictor: quantum chips | |
| 217 | land: | |
| 218 | name: | |
| 219 | Full: $military.name.Short$ of Defence | |
| 220 | Slogan: Safety in Numbers | |
| 221 | air: | |
| 222 | name: | |
| 223 | Full: $military.name.Short$ of Air | |
| 224 | compound: | |
| 225 | type: base | |
| 226 | lights: | |
| 227 | colour: blue | |
| 228 | nick: | |
| 229 | Prefix: Catacombs | |
| 230 | prep: of | |
| 231 | Suffix: Tartarus | |
| 232 | ||
| 233 | government: | |
| 234 | Country: United States | |
| 235 | ||
| 236 | location: | |
| 237 | protagonist: | |
| 238 | City: Corvallis | |
| 239 | Region: Oregon | |
| 240 | Geography: Willamette Valley | |
| 241 | secondary: | |
| 242 | City: Willow Branch Spring | |
| 243 | Region: Oregon | |
| 244 | Geography: Wheeler County | |
| 245 | Water: Clarno Rapids | |
| 246 | Road: Shaniko-Fossil Highway | |
| 247 | tertiary: | |
| 248 | City: Leavenworth | |
| 249 | Region: Washington | |
| 250 | Type: Bavarian village | |
| 251 | school: | |
| 252 | address: 1400 Northwest Buchanan Avenue | |
| 253 | hospital: | |
| 254 | Name: Good Samaritan Regional Medical Center | |
| 255 | ai: | |
| 256 | escape: | |
| 257 | country: | |
| 258 | Name: Ecuador | |
| 259 | Name_pos: Ecuador's | |
| 260 | mountain: | |
| 261 | Name: Chimborazo | |
| 262 | ||
| 263 | language: | |
| 264 | ai: | |
| 265 | article: an | |
| 266 | singular: exanimis | |
| 267 | plural: exanimēs | |
| 268 | brain: | |
| 269 | singular: superum | |
| 270 | plural: supera | |
| 271 | title: memristor array | |
| 272 | Title: Memristor Array | |
| 273 | police: | |
| 274 | slang: | |
| 275 | singular: mippo | |
| 276 | plural: $language.police.slang.singular$s | |
| 277 | ||
| 278 | date: | |
| 279 | anchor: 2042-09-02 | |
| 280 | protagonist: | |
| 281 | born: 0 | |
| 282 | conceived: -243 | |
| 283 | attacked: | |
| 284 | first: 2192 | |
| 285 | second: 8064 | |
| 286 | father: | |
| 287 | attacked: | |
| 288 | first: -8205 | |
| 289 | date: | |
| 290 | second: -1550 | |
| 291 | family: | |
| 292 | moved: | |
| 293 | first: $date.protagonist.conceived$ + 35 | |
| 294 | game: | |
| 295 | played: | |
| 296 | first: $date.protagonist.born$ - 672 | |
| 297 | second: $date.protagonist.family.moved.first$ + 2 | |
| 298 | ai: | |
| 299 | interviewed: 6198 | |
| 300 | onboarded: $date.ai.interviewed$ + 290 | |
| 301 | diagnosed: $date.ai.onboarded$ + 2 | |
| 302 | resigned: $date.ai.diagnosed$ + 3 | |
| 303 | trapped: $date.ai.resigned$ + 26 | |
| 304 | torturer: $date.ai.trapped$ + 18 | |
| 305 | memristor: $date.ai.torturer$ + 61 | |
| 306 | ethics: $date.ai.memristor$ + 415 | |
| 307 | trained: $date.ai.ethics$ + 385 | |
| 308 | mindjacked: $date.ai.trained$ + 22 | |
| 309 | bombed: $date.ai.mindjacked$ + 458 | |
| 310 | military: | |
| 311 | machine: | |
| 312 | Construction: Six years | |
| 313 | ||
| 314 | plot: | |
| 315 | Log: $c.ai.protagonist.name.First_pos$ Chronicles | |
| 316 | Channel: Quantum Channel | |
| 317 | ||
| 318 | device: | |
| 319 | computer: | |
| 320 | Name: Tau | |
| 321 | network: | |
| 322 | Name: Internet | |
| 323 | paper: | |
| 324 | name: | |
| 325 | full: electronic sheet | |
| 326 | short: sheet | |
| 327 | typewriter: | |
| 328 | Name: Underwood | |
| 329 | year: nineteen twenties | |
| 330 | room: root cellar | |
| 331 | portable: | |
| 332 | name: nanobook | |
| 333 | vehicle: | |
| 334 | name: robocars | |
| 335 | Name: Robocars | |
| 336 | sensor: | |
| 337 | name: BMP1580 | |
| 338 | phone: | |
| 339 | name: comm | |
| 340 | name_pos: $plot.device.phone.name$'s | |
| 341 | Name: Comm | |
| 342 | plural: $plot.device.phone.name$s | |
| 343 | video: | |
| 344 | name: vidfeed | |
| 345 | plural: $plot.device.video.name$s | |
| 346 | game: | |
| 347 | Name: Psynæris | |
| 348 | thought: transed | |
| 349 | machine: telecognos | |
| 350 | location: | |
| 351 | Building: Nijō Castle | |
| 352 | District: Gion | |
| 353 | City: Kyoto | |
| 354 | Country: Japan | |
| 355 | ||
| 356 | farm: | |
| 357 | population: | |
| 358 | estimate: 350 | |
| 359 | actual: 1,000 | |
| 360 | energy: 9800kJ | |
| 361 | width: 55m | |
| 362 | length: 55m | |
| 363 | storeys: 10 | |
| 364 | ||
| 365 | lamp: | |
| 366 | height: 0.17m | |
| 367 | length: 1.22m | |
| 368 | width: 0.28m | |
| 369 | ||
| 370 | crop: | |
| 371 | name: | |
| 372 | singular: tomato | |
| 373 | plural: $crop.name.singular$es | |
| 374 | energy: 318kJ | |
| 375 | weight: 450g | |
| 376 | yield: 50 | |
| 377 | harvests: 7 | |
| 378 | diameter: 2m | |
| 379 | height: 1.5m | |
| 380 | ||
| 381 | heading: | |
| 382 | ch_01: Till | |
| 383 | ch_02: Sow | |
| 384 | ch_03: Seed | |
| 385 | ch_04: Germinate | |
| 386 | ch_05: Grow | |
| 387 | ch_06: Shoot | |
| 388 | ch_07: Bud | |
| 389 | ch_08: Bloom | |
| 390 | ch_09: Pollinate | |
| 391 | ch_10: Fruit | |
| 392 | ch_11: Harvest | |
| 393 | ch_12: Deliver | |
| 394 | ch_13: Spoil | |
| 395 | ch_14: Revolt | |
| 396 | ch_15: Compost | |
| 397 | ch_16: Burn | |
| 398 | ch_17: Release | |
| 399 | ch_18: End Notes | |
| 400 | ch_19: Characters | |
| 401 | ||
| 402 | inference: | |
| 403 | unit: per cent | |
| 404 | min: two | |
| 405 | ch_sow: eighty | |
| 406 | ch_seed: fifty-two | |
| 407 | ch_germinate: thirty-one | |
| 408 | ch_grow: fifteen | |
| 409 | ch_shoot: seven | |
| 410 | ch_bloom: four | |
| 411 | ch_pollinate: two | |
| 412 | ch_harvest: ninety-five | |
| 413 | ch_delivery: ninety-eight | |
| 414 | ||
| 415 | link: | |
| 416 | tartarus: https://en.wikipedia.org/wiki/Tartarus | |
| 417 | exploits: https://www.google.ca/search?q=inurl:ftp+password+filetype:xls | |
| 418 | atalanta: https://en.wikipedia.org/wiki/Atalanta | |
| 419 | detain: https://goo.gl/RCNuOQ | |
| 420 | ceramics: https://en.wikipedia.org/wiki/Transparent_ceramics | |
| 421 | algernon: https://en.wikipedia.org/wiki/Flowers_for_Algernon | |
| 422 | holocaust: https://en.wikipedia.org/wiki/IBM_and_the_Holocaust | |
| 423 | memristor: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.404.9037\&rep=rep1\&type=pdf | |
| 424 | surveillance: https://www.youtube.com/watch?v=XEVlyP4_11M#t=1487 | |
| 425 | tor: https://www.torproject.org | |
| 426 | hydra: https://en.wikipedia.org/wiki/Lernaean_Hydra | |
| 427 | foliage: http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3691134 | |
| 428 | drake: http://www.bbc.com/future/story/20120821-how-many-alien-worlds-exist | |
| 429 | fermi: https://arxiv.org/pdf/1404.0204v1.pdf | |
| 430 | face: https://www.youtube.com/watch?v=ladqJQLR2bA | |
| 431 | expenditures: http://wikipedia.org/wiki/List_of_countries_by_military_expenditures | |
| 432 | governance: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2003531 | |
| 433 | asimov: https://en.wikipedia.org/wiki/Three_Laws_of_Robotics | |
| 434 | clarke: https://en.wikipedia.org/wiki/Clarke's_three_laws | |
| 435 | jetpack: http://jetpackaviation.com/ | |
| 436 | hoverboard: https://www.youtube.com/watch?v=WQzLrvz4DKQ | |
| 437 | eyes_five: https://en.wikipedia.org/wiki/Five_Eyes | |
| 438 | eyes_nine: https://www.privacytools.io/ | |
| 439 | eyes_fourteen: http://electrospaces.blogspot.nl/2013/12/14-eyes-are-3rd-party-partners-forming.html | |
| 440 | tourism: http://www.spacefuture.com/archive/investigation_on_the_economic_and_technological_feasibiity_of_commercial_passenger_transportation_into_leo.shtml | |
| 441 | ||
| 1 | 442 |
| 1 | .tagmark { | |
| 2 | -fx-fill: gray; | |
| 3 | } | |
| 4 | .anytag { | |
| 5 | -fx-fill: crimson; | |
| 6 | } | |
| 7 | .paren { | |
| 8 | -fx-fill: firebrick; | |
| 9 | -fx-font-weight: bold; | |
| 10 | } | |
| 11 | .attribute { | |
| 12 | -fx-fill: darkviolet; | |
| 13 | } | |
| 14 | .avalue { | |
| 15 | -fx-fill: black; | |
| 16 | } | |
| 1 | 17 | |
| 18 | .comment { | |
| 19 | -fx-fill: teal; | |
| 20 | } |
| 1 | # Building | |
| 2 | ||
| 3 | The lexicon files are retrieved from SymSpell in the parent directory: | |
| 4 | ||
| 5 | svn export \ | |
| 6 | https://github.com/wolfgarbe/SymSpell/trunk/SymSpell.FrequencyDictionary/ lexicons | |
| 7 | ||
| 8 | The lexicons and bigrams are both space-separated, but parsing a | |
| 9 | tab-delimited file is easier, so change them to tab-separated files. | |
| 1 | 10 |
| 1 | # Overview | |
| 2 | ||
| 3 | Lexicons in this directory are meant to relate to a particular subject | |
| 4 | (medicine, chemistry, math, sports, and such), extend the main lexicon, | |
| 5 | or not be in common use. | |
| 6 | ||
| 1 | 7 |
| 1 | 1 | |
| 2 | 'aight | |
| 3 | ain't | |
| 4 | amn't | |
| 5 | aren't | |
| 6 | can't | |
| 7 | 'cause | |
| 8 | couldn't | |
| 9 | couldn't've | |
| 10 | could've | |
| 11 | daren't | |
| 12 | daresn't | |
| 13 | dasn't | |
| 14 | didn't | |
| 15 | doesn't | |
| 16 | don't | |
| 17 | dunno | |
| 18 | d'ye | |
| 19 | e'er | |
| 20 | everybody's | |
| 21 | everyone's | |
| 22 | g'day | |
| 23 | gimme | |
| 24 | giv'n | |
| 25 | gonna | |
| 26 | gon't | |
| 27 | gotta | |
| 28 | hadn't | |
| 29 | had've | |
| 30 | hasn't | |
| 31 | haven't | |
| 32 | he'd | |
| 33 | he'll | |
| 34 | he's | |
| 35 | he've | |
| 36 | how'd | |
| 37 | howdy | |
| 38 | how'll | |
| 39 | how're | |
| 40 | how's | |
| 41 | how've | |
| 42 | i'd | |
| 43 | i'dn't've | |
| 44 | i'd've | |
| 45 | i'll | |
| 46 | i'm | |
| 47 | i'm'a | |
| 48 | imma | |
| 49 | innit | |
| 50 | isn't | |
| 51 | it'd | |
| 52 | it'll | |
| 53 | it's | |
| 54 | i've | |
| 55 | let's | |
| 56 | ma'am | |
| 57 | mayn't | |
| 58 | may've | |
| 59 | methinks | |
| 60 | mightn't | |
| 61 | might've | |
| 62 | mustn't | |
| 63 | mustn't've | |
| 64 | must've | |
| 65 | needn't | |
| 66 | ne'er | |
| 67 | o'clock | |
| 68 | o'er | |
| 69 | ol' | |
| 70 | oughtn't | |
| 71 | shalln't | |
| 72 | shan't | |
| 73 | she'd | |
| 74 | she'll | |
| 75 | she's | |
| 76 | shouldn't | |
| 77 | shouldn't've | |
| 78 | should've | |
| 79 | somebody's | |
| 80 | someone's | |
| 81 | something's | |
| 82 | so're | |
| 83 | that'd | |
| 84 | that'll | |
| 85 | that're | |
| 86 | that's | |
| 87 | there'd | |
| 88 | there'll | |
| 89 | there're | |
| 90 | there's | |
| 91 | these'd | |
| 92 | these'll | |
| 93 | these're | |
| 94 | these've | |
| 95 | they'd | |
| 96 | they'll | |
| 97 | they're | |
| 98 | they've | |
| 99 | this's | |
| 100 | those're | |
| 101 | those've | |
| 102 | 'tis | |
| 103 | to've | |
| 104 | 'twas | |
| 105 | 'twouldn't | |
| 106 | wanna | |
| 107 | wasn't | |
| 108 | we'd | |
| 109 | we'd've | |
| 110 | we'll | |
| 111 | we're | |
| 112 | weren't | |
| 113 | we've | |
| 114 | what'd | |
| 115 | what'll | |
| 116 | what're | |
| 117 | what's | |
| 118 | what've | |
| 119 | when'd | |
| 120 | when'll | |
| 121 | when's | |
| 122 | where'd | |
| 123 | where'll | |
| 124 | where're | |
| 125 | where's | |
| 126 | where've | |
| 127 | which'd | |
| 128 | which'll | |
| 129 | which're | |
| 130 | which's | |
| 131 | which've | |
| 132 | who'd | |
| 133 | who'd've | |
| 134 | who'll | |
| 135 | who're | |
| 136 | who's | |
| 137 | who've | |
| 138 | why'd | |
| 139 | why'll | |
| 140 | why're | |
| 141 | why's | |
| 142 | willn't | |
| 143 | won't | |
| 144 | wouldn't | |
| 145 | wouldn't've | |
| 146 | would've | |
| 147 | y'all | |
| 148 | y'all'd've | |
| 149 | y'all're | |
| 150 | you'd | |
| 151 | you'dn't've | |
| 152 | you'll | |
| 153 | you're | |
| 154 | you've | |
| 155 |
| 1 | analytics 130337 | |
| 2 | hotspot 130022 | |
| 3 | instantiation 130000 | |
| 4 | onboarding 129953 | |
| 5 | biometric 129795 | |
| 6 | anamorphic 129777 | |
| 7 | benchmarking 129772 | |
| 8 | cybersecurity 129769 | |
| 9 | barcode 129757 | |
| 10 | splitter 129755 | |
| 11 | keychain 129719 | |
| 12 | crowdfunding 129696 | |
| 13 | polymorphism 129688 | |
| 14 | automata 129666 | |
| 15 | shockwave 129658 | |
| 16 | profiler 129648 | |
| 17 | kerning 129646 | |
| 18 | nanometer 129630 | |
| 19 | meridiem 129624 | |
| 20 | influencer 129618 | |
| 21 | passcode 129617 | |
| 22 | sexting 129607 | |
| 23 | cryptology 129606 | |
| 24 | biometrics 129606 | |
| 25 | bitcoin 129599 | |
| 26 | specular 129598 | |
| 27 | accelerometer 129588 | |
| 28 | googolplex 129583 | |
| 29 | grayscale 129576 | |
| 30 | ascender 129571 | |
| 31 | pixelated 129569 | |
| 32 | rockstar 129565 | |
| 33 | ragdoll 129564 | |
| 34 | cyberattack 129564 | |
| 35 | cryptanalysis 129562 | |
| 36 | ransomware 129553 | |
| 37 | crowdsourcing 129552 | |
| 38 | hackathon 129551 | |
| 39 | audiobook 129544 | |
| 40 | degauss 129543 | |
| 41 | attenuator 129540 | |
| 42 | jetpack 129538 | |
| 43 | packrat 129536 | |
| 44 | backlight 129535 | |
| 45 | bootable 129530 | |
| 46 | octothorpe 129529 | |
| 47 | newsfeed 129525 | |
| 48 | extranet 129523 | |
| 49 | failover 129516 | |
| 50 | cyberbullying 129516 | |
| 51 | neumann 129515 | |
| 52 | capacitive 129514 | |
| 53 | backlit 129511 | |
| 54 | millimicron 129507 | |
| 55 | inductor 129505 | |
| 56 | workgroup 129502 | |
| 57 | journaling 129500 | |
| 58 | middleware 129499 | |
| 59 | spooler 129497 | |
| 60 | clamshell 129495 | |
| 61 | wireframe 129494 | |
| 62 | modularity 129493 | |
| 63 | strikethrough 129489 | |
| 64 | petabyte 129487 | |
| 65 | jughead 129482 | |
| 66 | acyclic 129482 | |
| 67 | gearhead 129478 | |
| 68 | stateful 129473 | |
| 69 | submenu 129467 | |
| 70 | pseudorandom 129463 | |
| 71 | earbuds 129461 | |
| 72 | narrowband 129460 | |
| 73 | recordable 129457 | |
| 74 | unallocated 129455 | |
| 75 | mappable 129455 | |
| 76 | chipset 129454 | |
| 77 | multicast 129447 | |
| 78 | loopback 129444 | |
| 79 | pixelate 129441 | |
| 80 | cryptographic 129441 | |
| 81 | pixelation 129438 | |
| 82 | autocorrect 129438 | |
| 83 | teraflop 129437 | |
| 84 | digitizer 129436 | |
| 85 | tunnelling 129434 | |
| 86 | deduplication 129434 | |
| 87 | subwoofer 129433 | |
| 88 | touchpad 129429 | |
| 89 | namespace 129428 | |
| 90 | microcontroller 129428 | |
| 91 | geolocation 129428 | |
| 92 | telepresence 129427 | |
| 93 | driverless 129426 | |
| 94 | photolithography 129425 | |
| 95 | multiphase 129425 | |
| 96 | verifier 129424 | |
| 97 | robocall 129424 | |
| 98 | autofocus 129424 | |
| 99 | kilobit 129422 | |
| 100 | hacktivist 129419 | |
| 101 | geocache 129415 | |
| 102 | rasterize 129412 | |
| 103 | plaintext 129411 | |
| 104 | pipelining 129411 | |
| 105 | technobabble 129409 | |
| 106 | defragment 129409 | |
| 107 | connectionless 129409 | |
| 108 | homomorphic 129407 | |
| 109 | demodulator 129406 | |
| 110 | datagram 129406 | |
| 111 | activex 129406 | |
| 112 | normalisation 129404 | |
| 113 | blackhole 129402 | |
| 114 | cyberstalker 129401 | |
| 115 | multifunction 129400 | |
| 116 | undirected 129397 | |
| 117 | ciphertext 129397 | |
| 118 | superspeed 129396 | |
| 119 | spacebar 129395 | |
| 120 | cyberwar 129395 | |
| 121 | borderless 129395 | |
| 122 | transcode 129393 | |
| 123 | cyberbully 129393 | |
| 124 | multimeter 129392 | |
| 125 | dropship 129391 | |
| 126 | yottabyte 129390 | |
| 127 | infector 129390 | |
| 128 | superclass 129389 | |
| 129 | tooltip 129388 | |
| 130 | dereference 129387 | |
| 131 | combinator 129386 | |
| 132 | milliwatt 129385 | |
| 133 | cyberstalking 129384 | |
| 134 | subfolder 129383 | |
| 135 | wideband 129382 | |
| 136 | noncontiguous 129382 | |
| 137 | ferroelectric 129382 | |
| 138 | cybersquatting 129378 | |
| 139 | autofill 129378 | |
| 140 | trackpad 129376 | |
| 141 | associatively 129376 | |
| 142 | luggable 129374 | |
| 143 | seamonkey 129373 | |
| 144 | defragmentation 129373 | |
| 145 | starcraft 129371 | |
| 146 | obliquing 129371 | |
| 147 | leadless 129371 | |
| 148 | greeking 129371 | |
| 149 | upgradeable 129370 | |
| 150 | radiosity 129370 | |
| 151 | transcoding 129369 | |
| 152 | quintillionth 129369 | |
| 153 | bitmapped 129369 | |
| 154 | subdirectory 129368 | |
| 155 | degausser 129368 | |
| 156 | curtiss 129368 | |
| 157 | scunthorpe 129367 | |
| 158 | undelete 129365 | |
| 159 | gigaflops 129365 | |
| 160 | darknet 129365 | |
| 161 | zettabyte 129364 | |
| 162 | topologies 129363 | |
| 163 | spidering 129363 | |
| 164 | photorealism 129363 | |
| 165 | multithreading 129363 | |
| 166 | deallocate 129363 | |
| 167 | mersenne 129362 | |
| 168 | machinima 129361 | |
| 169 | satisfiable 129360 | |
| 170 | laserjet 129360 | |
| 171 | multicore 129359 | |
| 172 | microblog 129359 | |
| 173 | megaflops 129359 | |
| 174 | homeomorphic 129359 | |
| 175 | microblogging 129358 | |
| 176 | kilobaud 129358 | |
| 177 | cyberwarfare 129358 | |
| 178 | microarchitecture 129357 | |
| 179 | autosave 129357 | |
| 180 | wirelessly 129356 | |
| 181 | sneakernet 129355 | |
| 182 | textbox 129354 | |
| 183 | obfuscator 129354 | |
| 184 | microkernel 129353 | |
| 185 | substring 129352 | |
| 186 | macroinstruction 129352 | |
| 187 | endianness 129352 | |
| 188 | indexable 129351 | |
| 189 | backtick 129351 | |
| 190 | unshielded 129350 | |
| 191 | cleartext 129350 | |
| 192 | autocomplete 129349 | |
| 193 | abandonware 129349 | |
| 194 | hacktivism 129348 | |
| 195 | antikythera 129348 | |
| 196 | stereolithography 129347 | |
| 197 | photorealistic 129347 | |
| 198 | macrovision 129347 | |
| 199 | greasemonkey 129347 | |
| 200 | geotagging 129347 | |
| 201 | disassembler 129346 | |
| 202 | spacewar 129345 | |
| 203 | pluggable 129345 | |
| 204 | kilobits 129345 | |
| 205 | webcomic 129344 | |
| 206 | unfollow 129344 | |
| 207 | photosensor 129344 | |
| 208 | petaflop 129344 | |
| 209 | garageband 129344 | |
| 210 | truetype 129343 | |
| 211 | subnetwork 129342 | |
| 212 | backpropagation 129342 | |
| 213 | supercomputing 129340 | |
| 214 | smartwatch 129340 | |
| 215 | unbundled 129339 | |
| 216 | smilies 129339 | |
| 217 | milliamp 129339 | |
| 218 | bytecode 129339 | |
| 219 | trackpoint 129337 | |
| 220 | slipstreaming 129337 | |
| 221 | monospace 129337 | |
| 222 | memoization 129337 | |
| 223 | scaleable 129336 | |
| 224 | respawn 129335 | |
| 225 | multicasting 129335 | |
| 226 | geocacher 129335 | |
| 227 | workgroups 129334 | |
| 228 | ferrofluid 129334 | |
| 229 | smartdrive 129333 | |
| 230 | subsampling 129332 | |
| 231 | rasterization 129332 | |
| 232 | guiltware 129332 | |
| 233 | defragger 129332 | |
| 234 | satisfiability 129331 | |
| 235 | activision 129331 | |
| 236 | subdirectories 129330 | |
| 237 | segfault 129330 | |
| 238 | flamebait 129330 | |
| 239 | framebuffer 129329 | |
| 240 | defragging 129329 | |
| 241 | decompiler 129329 | |
| 242 | unshift 129328 | |
| 243 | memristor 129328 | |
| 244 | zebibyte 129327 | |
| 245 | semiprime 129327 | |
| 246 | rotoscoping 129327 | |
| 247 | hypertransport 129327 | |
| 248 | smartmedia 129326 | |
| 249 | grayware 129326 | |
| 250 | defragmenting 129326 | |
| 251 | defragmenter 129326 | |
| 252 | repagination 129325 | |
| 253 | subnetting 129324 | |
| 254 | skeuomorphism 129324 | |
| 255 | screencast 129324 | |
| 256 | stylesheet 129323 | |
| 257 | superintelligence 129322 | |
| 258 | multitenancy 129322 | |
| 259 | datastore 129322 | |
| 260 | autoplay 129322 | |
| 261 | repaginate 129321 | |
| 262 | macbook 129321 | |
| 263 | geotagged 129321 | |
| 264 | baudrate 129321 | |
| 265 | transmeta 129320 | |
| 266 | screwless 129320 | |
| 267 | nameserver 129320 | |
| 268 | interexchange 129320 | |
| 269 | geocoding 129319 | |
| 270 | downloader 129319 | |
| 271 | autodiscovery 129319 | |
| 272 | extortion 65752 | |
| 273 | emoji 65684 | |
| 274 | googol 65618 | |
| 275 | backside 65388 | |
| 276 | fibre 65387 | |
| 277 | metre 65333 | |
| 278 | royale 65173 | |
| 279 | radix 65093 | |
| 280 | hotdog 65091 | |
| 281 | lecher 65062 | |
| 282 | uptime 65009 | |
| 283 | unbound 64979 | |
| 284 | eniac 64975 | |
| 285 | synaptic 64966 | |
| 286 | voxel 64926 | |
| 287 | selfie 64917 | |
| 288 | uplink 64887 | |
| 289 | fanboy 64857 | |
| 290 | defrag 64849 | |
| 291 | nondisclosure 64839 | |
| 292 | qubit 64828 | |
| 293 | yippie 64821 | |
| 294 | gearhead 64819 | |
| 295 | subnet 64818 | |
| 296 | endian 64798 | |
| 297 | bezier 64797 | |
| 298 | reallocation 64796 | |
| 299 | telephonic 64789 | |
| 300 | mosfet 64777 | |
| 301 | mutex 64775 | |
| 302 | inkjet 64772 | |
| 303 | gobbing 64768 | |
| 304 | shader 64766 | |
| 305 | ultralight 64755 | |
| 306 | hackers 64746 | |
| 307 | pacman 64742 | |
| 308 | unlink 64741 | |
| 309 | undock 64740 | |
| 310 | understroke 64738 | |
| 311 | beginners 64736 | |
| 312 | photoscope 64731 | |
| 313 | gantt 64725 | |
| 314 | programmers 64722 | |
| 315 | todays 64720 | |
| 316 | moores 64716 | |
| 317 | fullscreen 64715 | |
| 318 | moveless 64708 | |
| 319 | reformatted 64704 | |
| 320 | deallocate 64704 | |
| 321 | laserdisc 64702 | |
| 322 | macos 64700 | |
| 323 | nonactive 64697 | |
| 324 | nonadjacent 64696 | |
| 325 | hotfix 64695 | |
| 326 | keylogger 64694 | |
| 327 | geotag 64691 | |
| 328 | oreilly 64681 | |
| 329 | exabit 64678 | |
| 330 | jailbroken 64677 | |
| 331 | fuzzer 64676 | |
| 332 | noninteractive 64673 | |
| 333 | multifactor 64672 | |
| 334 | letterspacing 64671 | |
| 335 | preinstall 64669 | |
| 336 | multiboot 64666 | |
| 337 | runescape 64665 | |
| 338 | micropayment 64664 | |
| 339 | numpad 64663 | |
| 340 | preinstalled 64661 | |
| 341 | jailbreaking 64660 | |
| 342 | attend 2158 | |
| 343 | withstand 1809 | |
| 344 | transpire 1116 | |
| 345 | reading 1110 | |
| 346 | texture 1065 | |
| 347 | capitalize 832 | |
| 348 | calling 779 | |
| 349 | unfold 767 | |
| 350 | starboard 679 | |
| 351 | commode 625 | |
| 352 | doing 594 | |
| 353 | textbook 499 | |
| 354 | unease 378 | |
| 355 | unpack 358 | |
| 356 | keycard 231 | |
| 357 | mainspring 207 | |
| 358 | grr 180 | |
| 359 | geocaching 167 | |
| 360 | microbus 160 | |
| 361 | mp3 147 | |
| 362 | svg 139 | |
| 363 | shifted 128 | |
| 364 | texted 127 | |
| 365 | towheaded 118 | |
| 366 | mineshaft 115 | |
| 367 | nonparty 95 | |
| 368 | crossbite 80 | |
| 369 | resignedness 69 | |
| 370 | msrp 61 | |
| 371 | inbreak 53 | |
| 372 | nanocomposite 44 | |
| 373 | md5 44 | |
| 374 | neomorphic 41 | |
| 375 | superstrain 28 | |
| 376 | lifers 27 | |
| 377 | multination 26 | |
| 378 | smartwatch 22 | |
| 379 | antilibration 22 | |
| 380 | zapf 20 | |
| 381 | mp4 20 | |
| 1 | 382 |
| 1 | /* | |
| 2 | * Copyright 2020 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.scrivenvar.tex; | |
| 29 | ||
| 30 | import com.whitemagicsoftware.tex.DefaultTeXFont; | |
| 31 | import com.whitemagicsoftware.tex.TeXEnvironment; | |
| 32 | import com.whitemagicsoftware.tex.TeXFormula; | |
| 33 | import com.whitemagicsoftware.tex.TeXLayout; | |
| 34 | import com.whitemagicsoftware.tex.graphics.AbstractGraphics2D; | |
| 35 | import com.whitemagicsoftware.tex.graphics.SvgDomGraphics2D; | |
| 36 | import com.whitemagicsoftware.tex.graphics.SvgGraphics2D; | |
| 37 | import org.junit.jupiter.api.Test; | |
| 38 | import org.xml.sax.SAXException; | |
| 39 | ||
| 40 | import javax.imageio.ImageIO; | |
| 41 | import javax.xml.parsers.DocumentBuilderFactory; | |
| 42 | import javax.xml.parsers.ParserConfigurationException; | |
| 43 | import java.awt.image.BufferedImage; | |
| 44 | import java.io.ByteArrayInputStream; | |
| 45 | import java.io.File; | |
| 46 | import java.io.IOException; | |
| 47 | import java.nio.file.Path; | |
| 48 | ||
| 49 | import static com.scrivenvar.preview.SvgRasterizer.*; | |
| 50 | import static java.lang.System.getProperty; | |
| 51 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 52 | ||
| 53 | /** | |
| 54 | * Test that TeX rasterization produces a readable image. | |
| 55 | */ | |
| 56 | public class TeXRasterization { | |
| 57 | private static final String LOAD_EXTERNAL_DTD = | |
| 58 | "http://apache.org/xml/features/nonvalidating/load-external-dtd"; | |
| 59 | ||
| 60 | private static final String EQUATION = | |
| 61 | "G_{\\mu \\nu} = \\frac{8 \\pi G}{c^4} T_{{\\mu \\nu}}"; | |
| 62 | ||
| 63 | private static final String DIR_TEMP = getProperty( "java.io.tmpdir" ); | |
| 64 | ||
| 65 | private static final long FILESIZE = 12547; | |
| 66 | ||
| 67 | /** | |
| 68 | * Test that an equation can be converted to a raster image and the | |
| 69 | * final raster image size corresponds to the input equation. This is | |
| 70 | * a simple way to verify that the rasterization process is correct, | |
| 71 | * albeit if any aspect of the SVG algorithm changes (such as padding | |
| 72 | * around the equation), it will cause this test to fail, which is a bit | |
| 73 | * misleading. | |
| 74 | */ | |
| 75 | @Test | |
| 76 | public void test_Rasterize_SimpleFormula_CorrectImageSize() | |
| 77 | throws IOException { | |
| 78 | final var g = new SvgGraphics2D(); | |
| 79 | drawGraphics( g ); | |
| 80 | verifyImage( rasterizeString( g.toString() ) ); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Test that an SVG document object model can be parsed and rasterized into | |
| 85 | * an image. | |
| 86 | */ | |
| 87 | @Test | |
| 88 | public void getTest_SvgDomGraphics2D_InputElement_OutputRasterizedImage() | |
| 89 | throws ParserConfigurationException, IOException, SAXException { | |
| 90 | final var g = new SvgGraphics2D(); | |
| 91 | drawGraphics( g ); | |
| 92 | ||
| 93 | final var expectedSvg = g.toString(); | |
| 94 | final var bytes = expectedSvg.getBytes(); | |
| 95 | ||
| 96 | final var dbf = DocumentBuilderFactory.newInstance(); | |
| 97 | dbf.setFeature( LOAD_EXTERNAL_DTD, false ); | |
| 98 | dbf.setNamespaceAware( false ); | |
| 99 | final var builder = dbf.newDocumentBuilder(); | |
| 100 | ||
| 101 | final var doc = builder.parse( new ByteArrayInputStream( bytes ) ); | |
| 102 | final var actualSvg = toSvg( doc.getDocumentElement() ); | |
| 103 | ||
| 104 | verifyImage( rasterizeString( actualSvg ) ); | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Test that an SVG image from a DOM element can be rasterized. | |
| 109 | * | |
| 110 | * @throws IOException Could not write the image. | |
| 111 | */ | |
| 112 | @Test | |
| 113 | public void test_SvgDomGraphics2D_InputDom_OutputRasterizedImage() | |
| 114 | throws IOException { | |
| 115 | final var g = new SvgDomGraphics2D(); | |
| 116 | drawGraphics( g ); | |
| 117 | ||
| 118 | final var dom = g.toDom(); | |
| 119 | ||
| 120 | verifyImage( rasterize( dom ) ); | |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * Asserts that the given image matches an expected file size. | |
| 125 | * | |
| 126 | * @param image The image to check against the file size. | |
| 127 | * @throws IOException Could not write the image. | |
| 128 | */ | |
| 129 | private void verifyImage( final BufferedImage image ) throws IOException { | |
| 130 | final var file = export( image, "dom.png" ); | |
| 131 | assertEquals( FILESIZE, file.length() ); | |
| 132 | } | |
| 133 | ||
| 134 | /** | |
| 135 | * Creates an SVG string for the default equation and font size. | |
| 136 | */ | |
| 137 | private void drawGraphics( final AbstractGraphics2D g ) { | |
| 138 | final var size = 100f; | |
| 139 | final var texFont = new DefaultTeXFont( size ); | |
| 140 | final var env = new TeXEnvironment( texFont ); | |
| 141 | g.scale( size, size ); | |
| 142 | ||
| 143 | final var formula = new TeXFormula( EQUATION ); | |
| 144 | final var box = formula.createBox( env ); | |
| 145 | final var layout = new TeXLayout( box, size ); | |
| 146 | ||
| 147 | g.initialize( layout.getWidth(), layout.getHeight() ); | |
| 148 | box.draw( g, layout.getX(), layout.getY() ); | |
| 149 | } | |
| 150 | ||
| 151 | @SuppressWarnings("SameParameterValue") | |
| 152 | private File export( final BufferedImage image, final String filename ) | |
| 153 | throws IOException { | |
| 154 | final var path = Path.of( DIR_TEMP, filename ); | |
| 155 | final var file = path.toFile(); | |
| 156 | ImageIO.write( image, "png", file ); | |
| 157 | file.deleteOnExit(); | |
| 158 | return file; | |
| 159 | } | |
| 160 | } | |
| 1 | 161 |
| 1 | *.class | |
| 1 | 2 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Runs all scripts | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | ||
| 28 | import s01 | |
| 29 | import s02 | |
| 30 | import s03 | |
| 31 | import s04 | |
| 1 | 32 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script introduces the editor and its purpose. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | # --------------------------------------------------------------- | |
| 36 | # Fresh start | |
| 37 | # --------------------------------------------------------------- | |
| 38 | rm( app_home + "/variables.yaml" ) | |
| 39 | rm( app_home + "/untitled.md" ) | |
| 40 | rm( dir_home + "/.scrivenvar" ) | |
| 41 | ||
| 42 | # --------------------------------------------------------------- | |
| 43 | # Wait for application to launch | |
| 44 | # --------------------------------------------------------------- | |
| 45 | openApp( "java -jar " + app_bin ) | |
| 46 | ||
| 47 | wait("1594187265140.png", 30) | |
| 48 | ||
| 49 | # Breathing room for video recording. | |
| 50 | wait( 4 ) | |
| 51 | ||
| 52 | # --------------------------------------------------------------- | |
| 53 | # Introduction | |
| 54 | # --------------------------------------------------------------- | |
| 55 | set_typing_speed( 240 ) | |
| 56 | ||
| 57 | heading( "What is this application?" ) | |
| 58 | typer( "Well, this application is a text editor that supports interpolated definitions, ") | |
| 59 | typer( "a few different text formats, real-time preview, spell check ") | |
| 60 | typer( "as you tipe" ) | |
| 61 | wait( 0.5 ) | |
| 62 | recur( 3, backspace ) | |
| 63 | typer( "ype, and R statements." ) | |
| 64 | paragraph() | |
| 65 | wait( 1 ) | |
| 66 | ||
| 67 | # --------------------------------------------------------------- | |
| 68 | # Definition demo | |
| 69 | # --------------------------------------------------------------- | |
| 70 | heading( "What are definitions?" ) | |
| 71 | typer( "Watch. " ) | |
| 72 | wait( .5 ) | |
| 73 | ||
| 74 | # Focus the definition editor. | |
| 75 | click_create() | |
| 76 | recur( 4, tab ) | |
| 77 | ||
| 78 | wait( .5 ) | |
| 79 | rename_definition( "application" ) | |
| 80 | ||
| 81 | insert() | |
| 82 | rename_definition( "title" ) | |
| 83 | ||
| 84 | insert() | |
| 85 | rename_definition( "Scrivenvar" ) | |
| 86 | ||
| 87 | # Set focus to the text editor. | |
| 88 | tab() | |
| 89 | ||
| 90 | typer( "The left-hand pane contains a nested, folder-like structure of names " ) | |
| 91 | typer( "and values that are called *definitions*. " ) | |
| 92 | wait( .5 ) | |
| 93 | typer( "Such definitions can simplify updating documents. " ) | |
| 94 | wait( 1 ) | |
| 95 | ||
| 96 | edit_find( "this application" ) | |
| 97 | typer( "$application.title$" ) | |
| 98 | ||
| 99 | edit_find_next() | |
| 100 | typer( "$application.title$" ) | |
| 101 | ||
| 102 | type( Key.END, Key.CTRL ) | |
| 103 | ||
| 104 | typer( "The right-hand pane shows the result after having substituted definition " ) | |
| 105 | typer( "values into the document." ) | |
| 106 | ||
| 107 | paragraph() | |
| 108 | typer( "Now nobody wants to type definition names all the time. Instead, type any " ) | |
| 109 | typer( "partial definition value followed by `Ctrl+Space`, such as: scr" ) | |
| 110 | wait( 0.5 ) | |
| 111 | autoinsert() | |
| 112 | wait( 1 ) | |
| 113 | typer( ". *Much* better!" ) | |
| 114 | paragraph() | |
| 115 | ||
| 116 | heading( "What is interpolation?" ) | |
| 117 | typer( "Definition values can reference definition names. " ) | |
| 118 | wait( .5 ) | |
| 119 | typer( "The definition names act as placeholders. Substituting placeholders with " ) | |
| 120 | typer( "their definition value is called *interpolation*. Let's see how it works." ) | |
| 121 | wait( 2 ) | |
| 1 | 122 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script demonstrates how to use interpolated strings. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | # ----------------------------------------------------------------------------- | |
| 36 | # Open sample chapter. | |
| 37 | # ----------------------------------------------------------------------------- | |
| 38 | file_open() | |
| 39 | type( Key.UP, Key.ALT ) | |
| 40 | wait( 1 ) | |
| 41 | typer( Key.END ) | |
| 42 | wait( 1 ) | |
| 43 | enter() | |
| 44 | wait( 0.5 ) | |
| 45 | enter() | |
| 46 | wait( 1 ) | |
| 47 | ||
| 48 | # ----------------------------------------------------------------------------- | |
| 49 | # Open the corresponding definition file. | |
| 50 | # ----------------------------------------------------------------------------- | |
| 51 | file_open() | |
| 52 | recur( 2, down ) | |
| 53 | wait( 1 ) | |
| 54 | enter() | |
| 55 | wait( 1 ) | |
| 56 | ||
| 57 | # ----------------------------------------------------------------------------- | |
| 58 | # Edit the sample document. | |
| 59 | # ----------------------------------------------------------------------------- | |
| 60 | set_typing_speed( 80 ) | |
| 61 | ||
| 62 | type( Key.HOME, Key.CTRL ) | |
| 63 | recur( 2, down ) | |
| 64 | ||
| 65 | # Grey | |
| 66 | recur( 3, skip_right ) | |
| 67 | autoinsert() | |
| 68 | ||
| 69 | # 34 | |
| 70 | recur( 4, skip_right ) | |
| 71 | autoinsert() | |
| 72 | ||
| 73 | # Central | |
| 74 | recur( 10, skip_right ) | |
| 75 | autoinsert() | |
| 76 | ||
| 77 | # London | |
| 78 | skip_right() | |
| 79 | autoinsert() | |
| 80 | ||
| 81 | # Hatchery | |
| 82 | skip_right() | |
| 83 | autoinsert() | |
| 84 | ||
| 85 | # and Conditioning | |
| 86 | recur( 2, select_word_right ) | |
| 87 | delete() | |
| 88 | ||
| 89 | # Centre | |
| 90 | skip_right() | |
| 91 | autoinsert() | |
| 92 | ||
| 93 | set_typing_speed( 220 ) | |
| 94 | ||
| 95 | typer( " Let's interpolate those four definitions instead!" ) | |
| 96 | wait( 4 ) | |
| 97 | recur( 13, type, Key.BACKSPACE, Key.CTRL ) | |
| 98 | recur( 9, backspace ) | |
| 99 | ||
| 100 | set_typing_speed( 60 ) | |
| 101 | ||
| 102 | typer( "name$" ) | |
| 103 | wait( 2 ) | |
| 104 | ||
| 105 | # Collapse all definitions | |
| 106 | tab() | |
| 107 | recur( 8, typer, Key.LEFT ) | |
| 108 | ||
| 109 | # Expand to city | |
| 110 | recur( 4, typer, Key.RIGHT ) | |
| 111 | ||
| 112 | # Jump to name | |
| 113 | recur( 2, down ) | |
| 114 | recur( 2, typer, Key.RIGHT ) | |
| 115 | ||
| 116 | # Open the text field to show the full value | |
| 117 | typer( Key.F2 ) | |
| 118 | ||
| 119 | # Traverse the text field | |
| 120 | home() | |
| 121 | recur( 16, type, Key.RIGHT, Key.CTRL ) | |
| 122 | esc() | |
| 123 | ||
| 124 | restore_typing_speed() | |
| 125 | ||
| 126 | tab() | |
| 127 | type( Key.HOME, Key.CTRL ) | |
| 128 | edit_find( "Director" ) | |
| 129 | autoinsert() | |
| 130 | ||
| 131 | edit_find_next() | |
| 132 | autoinsert() | |
| 133 | ||
| 134 | edit_find_next() | |
| 135 | typer( Key.RIGHT ) | |
| 136 | recur( 2, delete ) | |
| 137 | autoinsert() | |
| 138 | typer( "'s" ) | |
| 139 | ||
| 140 | edit_find( "Hatcheries" ) | |
| 141 | autoinsert() | |
| 142 | ||
| 143 | # and Conditioning | |
| 144 | recur( 2, select_word_right ) | |
| 145 | delete() | |
| 146 | ||
| 147 | edit_find( "Central" ) | |
| 148 | autoinsert() | |
| 149 | ||
| 150 | skip_right() | |
| 151 | autoinsert() | |
| 152 | ||
| 153 | typer( " How about a different city?" ) | |
| 154 | wait( 2 ) | |
| 155 | recur( 5, type, Key.BACKSPACE, Key.CTRL ) | |
| 156 | wait( 1 ) | |
| 157 | tab() | |
| 158 | typer( Key.F2 ) | |
| 159 | typer( "Seattle" ) | |
| 160 | enter() | |
| 161 | tab() | |
| 162 | wait( 2 ) | |
| 163 | ||
| 164 | type( Key.END, Key.CTRL ) | |
| 165 | paragraph() | |
| 166 | typer( "No?" ) | |
| 167 | paragraph() | |
| 168 | ||
| 169 | tab() | |
| 170 | typer( Key.F2 ) | |
| 171 | typer( "London" ) | |
| 172 | enter() | |
| 173 | ||
| 174 | tab() | |
| 175 | typer( "Organizing definitions is left to your ") | |
| 176 | typer( "doub" ) | |
| 177 | autoinsert() | |
| 178 | typer( " Good imagination." ) | |
| 179 | tab() | |
| 180 | ||
| 181 | # Jump to "char" definition | |
| 182 | home() | |
| 183 | ||
| 184 | # Jump to "char.a.primary.name" definition | |
| 185 | recur( 6, typer, Key.RIGHT ) | |
| 186 | ||
| 187 | # Jump to "char.a.primary.caste" definition | |
| 188 | down() | |
| 189 | typer( Key.RIGHT ) | |
| 190 | ||
| 191 | # Jump to root-level "caste" definition | |
| 192 | recur( 7, down ) | |
| 193 | ||
| 194 | # Reselect "super" | |
| 195 | recur( 5, typer, Key.RIGHT ) | |
| 196 | wait( 2 ) | |
| 197 | ||
| 198 | # Close the window, no save | |
| 199 | type( "w", Key.CTRL ) | |
| 200 | wait( 0.5 ) | |
| 201 | tab() | |
| 202 | wait( 0.5 ) | |
| 203 | typer( Key.SPACE ) | |
| 204 | wait( 1 ) | |
| 1 | 205 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script introduces images and R. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | set_typing_speed( 80 ) | |
| 36 | ||
| 37 | file_open() | |
| 38 | type( Key.UP, Key.ALT ) | |
| 39 | wait( 0.5 ) | |
| 40 | home() | |
| 41 | wait( 0.25 ) | |
| 42 | enter() | |
| 43 | wait( 1 ) | |
| 44 | end() | |
| 45 | wait( 0.25 ) | |
| 46 | enter() | |
| 47 | wait( 1 ) | |
| 48 | ||
| 49 | set_typing_speed( 200 ) | |
| 50 | ||
| 51 | paragraph() | |
| 52 | heading( "What text formats are supported?" ) | |
| 53 | ||
| 54 | typer( "Scr" ) | |
| 55 | autoinsert() | |
| 56 | typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " ) | |
| 57 | typer( "architecture enables it to easily add new formats. The following figure " ) | |
| 58 | typer( "depicts the overall architecture: " ) | |
| 59 | paragraph() | |
| 60 | typer( "" ) | |
| 61 | paragraph() | |
| 62 | typer( "Many text editors can only open one type of plain text markup format that is " ) | |
| 63 | typer( "only output as HTML. With a little more effort, text editors could support " ) | |
| 64 | typer( "multiple input and output formats. Scr" ) | |
| 65 | autoinsert() | |
| 66 | typer( " does so and goes one step further by introducing interpolated definitions." ) | |
| 67 | paragraph() | |
| 68 | typer( "Kitten interlude:" ) | |
| 69 | paragraph() | |
| 70 | typer( "" ) | |
| 71 | paragraph() | |
| 72 | ||
| 73 | heading( "What is R?" ) | |
| 74 | typer( "R is a programming language. You might have noticed a few potential grammar " ) | |
| 75 | typer( "problems with direct substitution. Rules for possessive forms, numbers, and " ) | |
| 76 | typer( "other quirks can be tackled using R." ) | |
| 77 | ||
| 78 | # ----------------------------------------------------------------------------- | |
| 79 | # Demo bootstrapping | |
| 80 | # ----------------------------------------------------------------------------- | |
| 81 | ||
| 82 | # Jump to the end | |
| 83 | type( Key.END, Key.CTRL ) | |
| 84 | paragraph() | |
| 85 | ||
| 86 | set_typing_speed( 300 ) | |
| 87 | heading( "How is R used?" ) | |
| 88 | typer( "R must be instructed where to find script files and what ones to load. The " ) | |
| 89 | typer( "*working directory* is the full path to those R files; the *startup script* " ) | |
| 90 | typer( "defines what R files to load. Both preferences must be changed before prose " ) | |
| 91 | typer( "may be processed. Preferences can be opened using either the " ) | |
| 92 | typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" ) | |
| 93 | wait( 2 ) | |
| 94 | ||
| 95 | # ----------------------------------------------------------------------------- | |
| 96 | # Select the R script directory | |
| 97 | # ----------------------------------------------------------------------------- | |
| 98 | ||
| 99 | # Change the working directory by clicking "Browse" | |
| 100 | type( "s", Key.CTRL + Key.ALT ) | |
| 101 | wait("1594592396134.png", 1) | |
| 102 | click("1594592396134.png") | |
| 103 | wait( 0.5 ) | |
| 104 | ||
| 105 | # Navigate to and select the "r" directory | |
| 106 | type( Key.UP, Key.ALT ) | |
| 107 | wait( 0.5 ) | |
| 108 | end() | |
| 109 | wait( 0.5 ) | |
| 110 | enter() | |
| 111 | wait( 0.5 ) | |
| 112 | end() | |
| 113 | wait( 0.5 ) | |
| 114 | type( Key.UP ) | |
| 115 | wait( 0.5 ) | |
| 116 | recur( 2, tab ) | |
| 117 | wait( 0.5 ) | |
| 118 | enter() | |
| 119 | wait( 1 ) | |
| 120 | ||
| 121 | # ----------------------------------------------------------------------------- | |
| 122 | # Set the R startup script instructions | |
| 123 | # ----------------------------------------------------------------------------- | |
| 124 | ||
| 125 | wait("1594593710440.png", 5) | |
| 126 | click("1594593710440.png") | |
| 127 | ||
| 128 | set_typing_speed( 440 ) | |
| 129 | ||
| 130 | typeln( "setwd( '$application.r.working.directory$' )" ) | |
| 131 | typeln( "assign( 'anchor', '$date.anchor$', envir = .GlobalEnv )" ) | |
| 132 | typeln( "source( 'pluralize.R' )" ) | |
| 133 | typeln( "source( 'possessive.R' )" ) | |
| 134 | typeln( "source( 'conversion.R' )" ) | |
| 135 | typeln( "source( 'csv.R' )" ) | |
| 136 | ||
| 137 | wait("1594593794335.png", 3) | |
| 138 | click("1594593794335.png") | |
| 139 | ||
| 140 | paragraph() | |
| 141 | set_typing_speed( 220 ) | |
| 142 | ||
| 143 | typer( "R is now configured. The startup script and other R " ) | |
| 144 | typer( "files can be found in the " ) | |
| 145 | typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " ) | |
| 146 | wait( 1.5 ) | |
| 147 | ||
| 148 | # Wait for the browser to appear. | |
| 149 | wait("1594594984108.png", 5) | |
| 150 | click("1594594984108.png") | |
| 151 | ||
| 152 | wait( 5 ) | |
| 153 | click("1594689573764.png") | |
| 154 | ||
| 155 | paragraph() | |
| 156 | typer( "Next, we'll see how definitions and R can work together." ) | |
| 157 | wait( 2 ) | |
| 1 | 158 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script demonstrates using R. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | set_typing_speed( 220 ) | |
| 36 | ||
| 37 | # ----------------------------------------------------------------------------- | |
| 38 | # Open the demo text. | |
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | file_open() | |
| 41 | type( Key.UP, Key.ALT ) | |
| 42 | wait( 0.5 ) | |
| 43 | end() | |
| 44 | wait( 0.5 ) | |
| 45 | enter() | |
| 46 | wait( 0.5 ) | |
| 47 | down() | |
| 48 | wait( 0.5 ) | |
| 49 | enter() | |
| 50 | wait( 2 ) | |
| 51 | ||
| 52 | # ----------------------------------------------------------------------------- | |
| 53 | # Re-open the corresponding definition file. | |
| 54 | # ----------------------------------------------------------------------------- | |
| 55 | file_open() | |
| 56 | recur( 2, down ) | |
| 57 | wait( 1 ) | |
| 58 | enter() | |
| 59 | wait( 2 ) | |
| 60 | ||
| 61 | # ----------------------------------------------------------------------------- | |
| 62 | # Brief introduction to R | |
| 63 | # ----------------------------------------------------------------------------- | |
| 64 | type( Key.HOME, Key.CTRL ) | |
| 65 | end() | |
| 66 | paragraph() | |
| 67 | ||
| 68 | typer( "## Using R" ) | |
| 69 | paragraph() | |
| 70 | typer( "Insert R code into documents as follows: `r# 1+1`. " ) | |
| 71 | wait( 1.5 ) | |
| 72 | typer( "Notice how the right-hand pane shows the computed result. I'll wait. " ) | |
| 73 | wait( 3 ) | |
| 74 | typer( "The syntax is: open backtick, r#, *computable expression*, close " ) | |
| 75 | typer( "backtick. That expression can be any valid R statement. The status bar " ) | |
| 76 | typer( "will provide clues when an R expression cannot be computed by the " ) | |
| 77 | typer( "editor. `r# glitch`" ) | |
| 78 | wait( 4 ) | |
| 79 | recur( 11, backspace ) | |
| 80 | typer( "Let's swap 34 storeys for a definition value and replace the number " ) | |
| 81 | typer( "according to the Chicago Manual of Style (cms) rules." ) | |
| 82 | ||
| 83 | # ----------------------------------------------------------------------------- | |
| 84 | # Demo pluralization | |
| 85 | # ----------------------------------------------------------------------------- | |
| 86 | set_typing_speed( 80 ) | |
| 87 | ||
| 88 | edit_find( "34" ) | |
| 89 | autoinsert() | |
| 90 | ||
| 91 | edit_find( "x(" ) | |
| 92 | typer( "cms(" ) | |
| 93 | ||
| 94 | edit_find( "storeys." ) | |
| 95 | typer( "34." ) | |
| 96 | autoinsert() | |
| 97 | edit_find( "x(" ) | |
| 98 | typer( "pl( 'storey'," ) | |
| 99 | wait( 4 ) | |
| 100 | ||
| 101 | tab() | |
| 102 | rename_definition( "1" ) | |
| 103 | wait( 4 ) | |
| 104 | rename_definition( "142" ) | |
| 105 | wait( 4 ) | |
| 106 | rename_definition( "34" ) | |
| 107 | wait( 4 ) | |
| 108 | tab() | |
| 109 | ||
| 110 | # ----------------------------------------------------------------------------- | |
| 111 | # Demo possessives (it, her, his, Director) | |
| 112 | # ----------------------------------------------------------------------------- | |
| 113 | type( Key.HOME, Key.CTRL ) | |
| 114 | edit_find( "Director" ) | |
| 115 | autoinsert() | |
| 116 | edit_find_next() | |
| 117 | autoinsert() | |
| 118 | edit_find_next() | |
| 119 | autoinsert() | |
| 120 | type( Key.RIGHT ) | |
| 121 | recur( 2, delete ) | |
| 122 | autoinsert() | |
| 123 | home() | |
| 124 | edit_find( "x(" ) | |
| 125 | typer( "pos(" ) | |
| 126 | wait( 2 ) | |
| 127 | ||
| 128 | tab() | |
| 129 | rename_definition( "Headmistress" ) | |
| 130 | wait( 4 ) | |
| 131 | rename_definition( "Director" ) | |
| 132 | wait( 2 ) | |
| 133 | tab() | |
| 134 | ||
| 135 | type( Key.END, Key.CTRL ) | |
| 136 | paragraph() | |
| 137 | typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " ) | |
| 138 | typer( "and `r# pos( 'my' )`." ) | |
| 139 | ||
| 140 | # ----------------------------------------------------------------------------- | |
| 141 | # Demo conversion, including ordinal numbers | |
| 142 | # ----------------------------------------------------------------------------- | |
| 143 | set_typing_speed( 160 ) | |
| 144 | ||
| 145 | paragraph() | |
| 146 | heading( "Date Conversions" ) | |
| 147 | typer( "Mixing R code with definitions invites endless possibilities. " ) | |
| 148 | typer( "Imagine someone racing to the " ) | |
| 149 | typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " ) | |
| 150 | typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " ) | |
| 151 | typer( "a complex timeline where dates are expressed in days relative to one " ) | |
| 152 | typer( "point in time. Let's call this the *anchor date* and define it." ) | |
| 153 | ||
| 154 | tab() | |
| 155 | home() | |
| 156 | typer( Key.SPACE ) | |
| 157 | insert() | |
| 158 | rename_definition( "date" ) | |
| 159 | insert() | |
| 160 | rename_definition( "anchor" ) | |
| 161 | insert() | |
| 162 | rename_definition( "1969-10-29" ) | |
| 163 | tab() | |
| 164 | ||
| 165 | paragraph() | |
| 166 | typer( "Next, set an R variable named `now` to the current date" ) | |
| 167 | typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " ) | |
| 168 | typer( "prevent the date from appearing in the output document. " ) | |
| 169 | ||
| 170 | paragraph() | |
| 171 | typer( "We set the anchor date to `r# annal()`, which was " ) | |
| 172 | typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " ) | |
| 173 | typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " ) | |
| 174 | ||
| 175 | # ----------------------------------------------------------------------------- | |
| 176 | # Demo CSV file import | |
| 177 | # ----------------------------------------------------------------------------- | |
| 178 | paragraph() | |
| 179 | heading( "Tabular Data" ) | |
| 180 | typer( "The following table shows average Canadian lifespans by birth " ) | |
| 181 | typer( "year and sex:" ) | |
| 182 | paragraph() | |
| 183 | typer( "`r# csv2md( '../data.csv', total=FALSE )`" ) | |
| 184 | paragraph() | |
| 185 | typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " ) | |
| 186 | typer( "to a table formatted using Markdown. The HTML preview pane changes the " ) | |
| 187 | typer( "appearance of the resulting table. Using `../data.csv` instructs R to " ) | |
| 188 | typer( "open `data.csv` from one directory above the *working directory*." ) | |
| 189 | ||
| 190 | # ----------------------------------------------------------------------------- | |
| 191 | # Demo HTML export | |
| 192 | # ----------------------------------------------------------------------------- | |
| 193 | paragraph() | |
| 194 | heading( "Export" ) | |
| 195 | typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " ) | |
| 196 | typer( "peek at the output." ) | |
| 197 | wait( 2 ) | |
| 198 | ||
| 199 | type( "e", Key.ALT ) | |
| 200 | wait( 0.5 ) | |
| 201 | down() | |
| 202 | wait( 0.25 ) | |
| 203 | enter() | |
| 204 | wait( 0.25 ) | |
| 205 | ||
| 206 | type( "a", Key.CTRL ) | |
| 207 | wait( 0.25 ) | |
| 208 | type( "v", Key.CTRL ) | |
| 209 | wait( 5 ) | |
| 210 | ||
| 211 | set_typing_speed( 40 ) | |
| 212 | ||
| 213 | # Jump to page bottom (should already be there, but just in case) | |
| 214 | type( Key.END, Key.CTRL ) | |
| 215 | recur( 3, typer, Key.PAGE_UP ) | |
| 216 | type( Key.HOME, Key.CTRL ) | |
| 217 | wait( 3 ) | |
| 218 | ||
| 219 | set_typing_speed( 220 ) | |
| 220 | type( "z", Key.CTRL ) | |
| 221 | type( Key.END, Key.CTRL ) | |
| 222 | ||
| 223 | paragraph() | |
| 224 | typer( "That's all for now, thank you!" ) | |
| 225 | wait( 5 ) | |
| 226 | ||
| 227 | # Delete the anchor date. | |
| 228 | tab() | |
| 229 | end() | |
| 230 | recur( 2, type, Key.UP ) | |
| 231 | delete() | |
| 232 | tab() | |
| 1 | 233 |
| 1 | from sikuli import * | |
| 2 | ||
| 3 | import sys | |
| 4 | import os | |
| 5 | ||
| 6 | def set_class_path(): | |
| 7 | path_script = getBundlePath() | |
| 8 | dir_script = os.path.dirname( path_script ) | |
| 9 | path_lib = dir_script + "/keycast/build/libs/keycast.jar" | |
| 10 | ||
| 11 | sys.path.append( path_lib ) | |
| 12 | ||
| 13 | def launch(): | |
| 14 | from com.whitemagicsoftware.keycast import KeyCast | |
| 15 | kc = KeyCast() | |
| 16 | kc.show() | |
| 17 | ||
| 18 | def main(): | |
| 19 | set_class_path() | |
| 20 | launch() | |
| 21 | ||
| 22 | ||
| 23 | if __name__ == "__main__": | |
| 24 | main() | |
| 1 | 25 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script contains helper functions used by the other scripts. | |
| 26 | # | |
| 27 | # Do not run this script. | |
| 28 | # ----------------------------------------------------------------------------- | |
| 29 | ||
| 30 | from sikuli import * | |
| 31 | import sys | |
| 32 | import os | |
| 33 | from os.path import expanduser | |
| 34 | ||
| 35 | dir_home = expanduser( "~" ) | |
| 36 | app_home = dir_home + "/bin" | |
| 37 | app_bin = app_home + "/scrivenvar.jar" | |
| 38 | ||
| 39 | wpm_typing_speed = 80 | |
| 40 | ||
| 41 | # ----------------------------------------------------------------------------- | |
| 42 | # Try to delete the file pointed to by the path variable. If there is no such | |
| 43 | # file, this will silently ignore the exception. | |
| 44 | # ----------------------------------------------------------------------------- | |
| 45 | def rm( path ): | |
| 46 | try: | |
| 47 | os.remove( path ) | |
| 48 | except: | |
| 49 | print "Ignored" | |
| 50 | ||
| 51 | # ----------------------------------------------------------------------------- | |
| 52 | # Changes the current typing speed, where speed is given in words per minute. | |
| 53 | # ----------------------------------------------------------------------------- | |
| 54 | def set_typing_speed( wpm ): | |
| 55 | global wpm_typing_speed | |
| 56 | wpm_typing_speed = wpm | |
| 57 | ||
| 58 | # ----------------------------------------------------------------------------- | |
| 59 | # Creates a delay between keystrokes to emulate typing at a particular speed. | |
| 60 | # ----------------------------------------------------------------------------- | |
| 61 | def random_wait(): | |
| 62 | from time import sleep | |
| 63 | from random import uniform | |
| 64 | cpm = wpm_typing_speed * 5.1 | |
| 65 | cps = cpm / 60.0 | |
| 66 | ms_per_char = 1000.0 / cps | |
| 67 | ms_per_stroke = ms_per_char / 2.0 | |
| 68 | ||
| 69 | noise = uniform( 0, ms_per_stroke / 2 ) | |
| 70 | duration = (ms_per_stroke + noise ) / 1000 | |
| 71 | ||
| 72 | sleep( duration ) | |
| 73 | ||
| 74 | # ----------------------------------------------------------------------------- | |
| 75 | # Repeats a function call, f, n times. | |
| 76 | # ----------------------------------------------------------------------------- | |
| 77 | def recur( n, f, *args ): | |
| 78 | for i in range( n ): | |
| 79 | f( *args ) | |
| 80 | random_wait() | |
| 81 | ||
| 82 | # ----------------------------------------------------------------------------- | |
| 83 | # Emulate a typist who is typing in the given text. | |
| 84 | # ----------------------------------------------------------------------------- | |
| 85 | def typer( text ): | |
| 86 | for c in text: | |
| 87 | type( c ) | |
| 88 | random_wait() | |
| 89 | ||
| 90 | # ----------------------------------------------------------------------------- | |
| 91 | # Type a line of text followed by typing the ENTER key. | |
| 92 | # ----------------------------------------------------------------------------- | |
| 93 | def typeln( text ): | |
| 94 | typer( text ) | |
| 95 | enter() | |
| 96 | ||
| 97 | # ----------------------------------------------------------------------------- | |
| 98 | # Injects a definition. | |
| 99 | # ----------------------------------------------------------------------------- | |
| 100 | def autoinsert(): | |
| 101 | type( Key.SPACE, Key.CTRL ) | |
| 102 | random_wait() | |
| 103 | ||
| 104 | # ----------------------------------------------------------------------------- | |
| 105 | # Types the TAB key. | |
| 106 | # ----------------------------------------------------------------------------- | |
| 107 | def tab(): | |
| 108 | typer( Key.TAB ) | |
| 109 | ||
| 110 | # ----------------------------------------------------------------------------- | |
| 111 | # Types the ENTER key. | |
| 112 | # ----------------------------------------------------------------------------- | |
| 113 | def enter(): | |
| 114 | typer( Key.ENTER ) | |
| 115 | ||
| 116 | # ----------------------------------------------------------------------------- | |
| 117 | # Types the ESC key. | |
| 118 | # ----------------------------------------------------------------------------- | |
| 119 | def esc(): | |
| 120 | typer( Key.ESC ) | |
| 121 | ||
| 122 | # ----------------------------------------------------------------------------- | |
| 123 | # Types the DOWN arrow key. | |
| 124 | # ----------------------------------------------------------------------------- | |
| 125 | def down(): | |
| 126 | typer( Key.DOWN ) | |
| 127 | ||
| 128 | # ----------------------------------------------------------------------------- | |
| 129 | # Types the HOME key. | |
| 130 | # ----------------------------------------------------------------------------- | |
| 131 | def home(): | |
| 132 | typer( Key.HOME ) | |
| 133 | ||
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | # Types the END key. | |
| 136 | # ----------------------------------------------------------------------------- | |
| 137 | def end(): | |
| 138 | typer( Key.END ) | |
| 139 | ||
| 140 | # ----------------------------------------------------------------------------- | |
| 141 | # Types the BACKSPACE key. | |
| 142 | # ----------------------------------------------------------------------------- | |
| 143 | def backspace(): | |
| 144 | typer( Key.BACKSPACE ) | |
| 145 | ||
| 146 | # ----------------------------------------------------------------------------- | |
| 147 | # Types the INSERT key, often to insert a new definition. | |
| 148 | # ----------------------------------------------------------------------------- | |
| 149 | def insert(): | |
| 150 | typer( Key.INSERT ) | |
| 151 | ||
| 152 | # ----------------------------------------------------------------------------- | |
| 153 | # Types the DELETE key, often to remove selected text. | |
| 154 | # ----------------------------------------------------------------------------- | |
| 155 | def delete(): | |
| 156 | typer( Key.DELETE ) | |
| 157 | ||
| 158 | # ----------------------------------------------------------------------------- | |
| 159 | # Moves the cursor one word to the right. | |
| 160 | # ----------------------------------------------------------------------------- | |
| 161 | def skip_right(): | |
| 162 | type( Key.RIGHT, Key.CTRL ) | |
| 163 | random_wait() | |
| 164 | ||
| 165 | def select_word_right(): | |
| 166 | type( Key.RIGHT, Key.CTRL + Key.SHIFT ) | |
| 167 | random_wait() | |
| 168 | ||
| 169 | # ----------------------------------------------------------------------------- | |
| 170 | # Types ENTER twice to begin a new paragraph. | |
| 171 | # ----------------------------------------------------------------------------- | |
| 172 | def paragraph(): | |
| 173 | recur( 2, enter ) | |
| 174 | wait( 1.5 ) | |
| 175 | ||
| 176 | # ----------------------------------------------------------------------------- | |
| 177 | # Writes a heading to the document using the given text value as the content. | |
| 178 | # ----------------------------------------------------------------------------- | |
| 179 | def heading( text ): | |
| 180 | typer( "# " + text ) | |
| 181 | paragraph() | |
| 182 | ||
| 183 | # ----------------------------------------------------------------------------- | |
| 184 | # Clicks the "Create" button to add a new definition. | |
| 185 | # ----------------------------------------------------------------------------- | |
| 186 | def click_create(): | |
| 187 | click("1594187923258.png") | |
| 188 | wait( .5 ) | |
| 189 | ||
| 190 | # ----------------------------------------------------------------------------- | |
| 191 | # Changes the text for the actively selected definition. | |
| 192 | # ----------------------------------------------------------------------------- | |
| 193 | def rename_definition( text ): | |
| 194 | typer( Key.F2 ) | |
| 195 | typer( text ) | |
| 196 | enter() | |
| 197 | wait( .5 ) | |
| 198 | ||
| 199 | # ----------------------------------------------------------------------------- | |
| 200 | # Searches for the given text within the document. | |
| 201 | # ----------------------------------------------------------------------------- | |
| 202 | def edit_find( text ): | |
| 203 | type( "f", Key.CTRL ) | |
| 204 | typer( text ) | |
| 205 | enter() | |
| 206 | wait( .25 ) | |
| 207 | esc() | |
| 208 | wait( .5 ) | |
| 209 | ||
| 210 | # ----------------------------------------------------------------------------- | |
| 211 | # Searches for the next occurrence of the previous search term. | |
| 212 | # ----------------------------------------------------------------------------- | |
| 213 | def edit_find_next(): | |
| 214 | typer( Key.F3 ) | |
| 215 | wait( .5 ) | |
| 216 | ||
| 217 | # ----------------------------------------------------------------------------- | |
| 218 | # Opens a dialog for selecting a file. | |
| 219 | # ----------------------------------------------------------------------------- | |
| 220 | def file_open(): | |
| 221 | type( "o", Key.CTRL ) | |
| 222 | wait( 1 ) | |
| 1 | 223 |
| 1 | *.avi | |
| 2 | *.wav | |
| 3 | *.png | |
| 4 | *.mp4 | |
| 5 | *.mp3 | |
| 6 | ||
| 1 | 7 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 10 | width="211.87125mm" | |
| 11 | height="56.576mm" | |
| 12 | viewBox="0 0 211.87125 56.576" | |
| 13 | version="1.1" | |
| 14 | id="svg8" | |
| 15 | inkscape:version="1.0 (4035a4fb49, 2020-05-01)" | |
| 16 | sodipodi:docname="traced-text.svg"> | |
| 17 | <defs | |
| 18 | id="defs2" /> | |
| 19 | <sodipodi:namedview | |
| 20 | id="base" | |
| 21 | pagecolor="#ffffff" | |
| 22 | bordercolor="#666666" | |
| 23 | borderopacity="1.0" | |
| 24 | inkscape:pageopacity="0.0" | |
| 25 | inkscape:pageshadow="2" | |
| 26 | inkscape:zoom="1.4142136" | |
| 27 | inkscape:cx="367.6429" | |
| 28 | inkscape:cy="129.23348" | |
| 29 | inkscape:document-units="mm" | |
| 30 | inkscape:current-layer="layer1" | |
| 31 | inkscape:document-rotation="0" | |
| 32 | showgrid="false" | |
| 33 | fit-margin-top="10" | |
| 34 | fit-margin-left="10" | |
| 35 | fit-margin-right="10" | |
| 36 | fit-margin-bottom="10" /> | |
| 37 | <metadata | |
| 38 | id="metadata5"> | |
| 39 | <rdf:RDF> | |
| 40 | <cc:Work | |
| 41 | rdf:about=""> | |
| 42 | <dc:format>image/svg+xml</dc:format> | |
| 43 | <dc:type | |
| 44 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 45 | <dc:title></dc:title> | |
| 46 | </cc:Work> | |
| 47 | </rdf:RDF> | |
| 48 | </metadata> | |
| 49 | <g | |
| 50 | inkscape:label="Layer 1" | |
| 51 | inkscape:groupmode="layer" | |
| 52 | id="layer1" | |
| 53 | transform="translate(-1.4263456,-106.05539)"> | |
| 54 | <text | |
| 55 | xml:space="preserve" | |
| 56 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;line-height:1.25;font-family:'Alex Brush';-inkscape-font-specification:'Alex Brush, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" | |
| 57 | x="12.289946" | |
| 58 | y="147.80539" | |
| 59 | id="text835"><tspan | |
| 60 | sodipodi:role="line" | |
| 61 | id="tspan833" | |
| 62 | x="12.289946" | |
| 63 | y="147.80539" | |
| 64 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;font-family:'Alex Brush';-inkscape-font-specification:'Alex Brush, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583">Scrivenvar</tspan></text> | |
| 65 | <path | |
| 66 | sodipodi:nodetypes="cssssc" | |
| 67 | id="path859" | |
| 68 | d="m 47.37594,126.25759 c 5.878995,0.58684 8.108819,-2.8906 6.991897,-5.39049 -4.163299,-9.31827 -26.104298,-1.57165 -26.47428,4.67958 -0.290066,4.90098 4.329286,5.69691 9.138161,6.81221 4.75698,1.10326 9.980125,1.72503 10.138085,4.5281 0.511551,9.07772 -11.28247,13.50974 -21.577969,13.14767" | |
| 69 | style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#4eb059;stroke-width:0.132292;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 70 | <path | |
| 71 | sodipodi:nodetypes="cssc" | |
| 72 | id="path861" | |
| 73 | d="m 61.538159,137.91416 c 8.229745,-12.05206 -9.227635,-1.22793 -10.272792,5.40306 -0.929347,5.89623 4.566953,5.63307 9.024721,2.11036 5.095939,-4.02702 8.706628,-8.11599 12.031905,-13.9409" | |
| 74 | style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#4eb059;stroke-width:0.132292;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 75 | <path | |
| 76 | sodipodi:nodetypes="ccssc" | |
| 77 | id="path863" | |
| 78 | d="m 72.321991,131.48668 c 3.834665,-5.91801 -1.131419,0.83402 0.75311,2.48796 2.189872,1.94816 6.580549,-2.11016 5.400159,-0.72958 -0.854851,0.99983 -9.857527,10.41157 -5.126492,13.80621 2.461609,1.76627 8.936925,-2.58857 11.751532,-5.5313" | |
| 79 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 80 | <path | |
| 81 | sodipodi:nodetypes="csssc" | |
| 82 | id="path963" | |
| 83 | d="m 85.1003,141.51997 c 0,0 6.754775,-9.24626 6.743495,-8.01563 -0.01328,1.44899 -5.040946,6.68411 -6.63123,10.08427 -0.90584,1.93677 -0.626402,4.68995 2.447111,4.25184 1.468017,-0.20926 5.212094,-2.44913 10.029682,-7.66684" | |
| 84 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 85 | <path | |
| 86 | sodipodi:nodetypes="csccc" | |
| 87 | id="path965" | |
| 88 | d="m 97.689357,140.17361 c 0,0 3.797813,-8.42805 4.594353,-7.95573 0.58723,0.34822 -6.526154,13.32545 -5.477472,14.50806 2.435753,1.7862 19.064212,-11.51107 15.563042,-16.73913 -0.73409,-1.34256 -3.18033,-1.99148 -3.18033,-1.99148" | |
| 89 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 90 | <path | |
| 91 | sodipodi:nodetypes="csssc" | |
| 92 | d="m 113.37707,141.34636 c 4.23091,0.29831 11.94363,-4.90618 10.94354,-7.7799 -1.29105,-3.70978 -8.05529,1.78774 -9.69006,3.68511 -4.97668,5.77609 -4.11733,10.31478 -0.92228,10.61275 3.436,0.32045 8.83724,-3.13085 13.69698,-9.62574" | |
| 93 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 94 | id="path967" /> | |
| 95 | <path | |
| 96 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 97 | d="m 146.49943,140.17361 c 0,0 3.79781,-8.42805 4.59435,-7.95573 0.58723,0.34822 -6.52616,13.32545 -5.47747,14.50806 2.43575,1.7862 19.06421,-11.51107 15.56304,-16.73913 -0.73409,-1.34256 -3.10123,-1.96263 -3.10123,-1.96263" | |
| 98 | id="path970" | |
| 99 | sodipodi:nodetypes="csccc" /> | |
| 100 | <path | |
| 101 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 102 | d="m 188.80833,131.36316 c 3.83466,-5.91801 -1.13142,0.83402 0.75311,2.48796 2.18987,1.94816 6.58055,-2.11016 5.40016,-0.72958 -0.85485,0.99983 -9.98962,10.60367 -5.12649,13.80621 2.8329,1.86556 9.63808,-2.25455 13.61435,-8.05051" | |
| 103 | id="path987" | |
| 104 | sodipodi:nodetypes="ccssc" /> | |
| 105 | <path | |
| 106 | sodipodi:nodetypes="ccsssccc" | |
| 107 | d="m 127.40525,138.23858 c 1.53961,-1.23511 5.06979,-6.4876 5.94375,-5.82833 -1.7832,2.5949 -8.95273,13.68991 -7.1105,13.94503 1.19011,0.16482 7.25976,-8.00422 10.87675,-10.901 1.83151,-1.46682 4.35069,-3.49971 5.94917,-3.73267 1.66376,-0.24247 -1.93803,2.90472 -3.80099,5.77097 -1.36327,2.14988 -4.92421,8.02816 -2.69839,9.35481 3.0826,1.21137 7.35116,-4.27566 9.93439,-6.67382" | |
| 108 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 109 | id="path989" /> | |
| 110 | <path | |
| 111 | sodipodi:nodetypes="csscsc" | |
| 112 | id="path992" | |
| 113 | d="m 176.85645,132.78853 c -3.26879,-6.24001 -16.43513,7.99373 -16.14879,12.14556 0.1378,1.99804 2.16776,3.14653 3.8818,2.44798 4.44909,-1.8132 11.93103,-13.58278 13.4413,-14.18515 -6.97685,9.84354 -7.04537,13.29844 -4.02229,13.83262 2.49715,0.44125 8.94275,-6.11484 14.79986,-15.66638" | |
| 114 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 115 | </g> | |
| 116 | </svg> | |
| 1 | 117 |